20190728のRubyに関する記事は28件です。

merge

別テーブルから値を持ってきたい時

merge
.merge(item_id:@item.id,user_id:@user.id)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スタートアップで働いて得られた開発tips

スタートアップに入り1年以上が経ちました。日々目まぐるしく環境が変化していく中でやってしまった失敗。
どんな失敗に遭遇し、どう対応したかを簡単にまとめてみました!

ぜひ、参考になれば幸いです!
(もし、需要がありそうな項目があれば深掘りして再投稿してみます)

【フロント・サーバーサイド】

ページのレスポンス速度向上

当初とにかく指摘されたのはサイトのレスポンス速度でした。
そこで、レスポンス速度向上に取り組み、
結果的にPCのトップページの評価が40点台MAX96点にまで向上させることができました。
ちなみにモバイルは70点付近です(笑)
測定ツールはPageSpeed Insights(ページの読み込み時間の測定・改善策の提案をしてくれるサイト)です。

【PCでは96点】
スクリーンショット 2019-07-28 21.22.07.png

行った対策は以下です。

【インフラ系】

ElasticBeanstalkのインスタンスタイプはElasticBeanstalkから変更する

ElasticBeanstalkで環境を作成している場合、EC2からインスタンスタイプの変更しないようにしましょう。
依存関係などを崩してしまうため、うまく行きません。(僕はこれでサーバーを半日落しました(泣))
僕はこの時、環境の再構築をすることでサーバーの復活を果たすことができました。

Elastic Beanstalk > ダッシュボード > アクション > 環境の再構築

エラーページは設定しておく

こちらはサーバーを落としてしまった際、またはメンテナンスの際に503画面が表示されてしまうとユーザーの不信感につながりかねません。404や500はrails側で設定できますが503はサーバーが落ちてしまっているのでインフラの方で設定して置かなければなりません。

CloudFrontのCustom Error Responseを利用して、S3上にあるSorryページを表示する

スロークエリの設定

クエリの重さは、目に見えているページだけでなくサーバーにも大きな負担をかけてしまいます。
その結果、バックグラウンドでも影響が出てしまうことがあります。(メールが大量に届くor届かなくなる、またはjobなど)常にチェックできるように設定しておきましょう!

Amazon RDS for MySQLでスロークエリーログを出力させる手順

ヘルスが変化した際の通知

様々な要因がありますが、ヘルスがSevereになって気づいたら数時間サーバーが止まってしまっていた。なんてことにならないようにヘルス変化は常に通知しておきましょう。Elastic Beanstalkで環境を構築している際は

Elastic Beanstalk > 設定 > 通知 > 通知したい先のアドレス設定 > 適用

で設定できます!

【その他】

ドキュメント管理で効率的に時間を活用する

僕の独自の共有に時間がかかるランキングは

1位 開発フローの把握
2位 システムの環境構築
3位 ユーザーからのお問い合わせ対応

特に上位2つは出入りの激しい(笑)スタートアップの開発リソースを減らしてしまう深刻な問題だと思います。
一番対応の簡単な方法としてドキュメント管理に力を入れています。
もし、こんな質問がきたらこのマニュアルを渡すのようなパターンができていればいいドキュメント管理ができているなと思うようになりました。
(ドキュメントはGithubのwikiにまとめています)

Githubのタグを使い、新加入のエンジニアのキャッチアップ迅速に

ドキュメント管理関連でもう一つ。
途中から参加したエンジニアさんはシステムの把握に時間がかかると思います。
システムの把握に時間をかけすぎてしまうのはもったいないのですが、かけなさすぎると思いがけないバグを生んでしまいます。これがデータ保存系・更新系だと対応が大変です。。

そこで僕がよく使うのはGithubのアサインタグ付けです。
心がけていることは

①自分が担当するissue・pullrequestには必ずself assignする
②ある程度シリーズ化したorしそうなissue・pullrequestにはタグづけをする

この2つを徹底すると以下のことがおきます。
→タグ一つ検索するだけで誰がどのタスクを担当し。どんなコードを書いたかがわかる
→上記がわかるとトラブルやバグ仕様の把握をしたいときに誰とコミュニケーションをとればいいかがすぐわかる

この流れが出来上がると、間に常勤のエンジニアが入ってコミュニケーションの橋渡しをせずに済み、コミュニケーションコストが大幅に下がります!

【イメージ】
rails本家のコードを使わせていただきました。本当はこんな開発者おりません。
スクリーンショット 2019-07-28 22.50.33.png

【タグでソート】
スクリーンショット 2019-07-28 22.55.11.png

【アサイン者でソート】
スクリーンショット 2019-07-28 22.57.07.png

おまけ

副業案件お待ちしております。得意分野はRuby on Railsです!
職務経歴書

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

Rubyで二重コロン[::]とドット[.]の使い分け

背景・目的

二重コロン[::]とドット[.]の使い分けがごっちゃになる時があるので整理しておく。

二重コロン[::]とドット[.]の使い分け

一般的なルールは、、、
定数・クラス・モジュールを呼び出す場合 => 二重コロン[::]
メソッドを呼び出す場合 => ドット[.]

但し、、、
定数を呼び出す時にドット[.]を使うと怒られるが、メソッドを使う時に二重コロン[::]を使っても怒られないので私の様な初心者は注意が必要。

module Sample
    class User
        NAME = 'taro'

        def self.hello(name = NAME)
            "Hello, #{name}."
        end 
    end
end
Sample::User::NAME 
=> "taro"
Sample::User.NAME 
=> NoMethodError (undefined method 'NAME' for Sample::User:Class)
Sample::User::hello
=> "Hello, taro."
`Sample::User.hello
=> "Hello, taro."
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsのヘルパーメソッドで<script>タグを書く。

ビューヘルパーって慣れない内は難しいよね

結構コロンがいるのかとか、どこに括弧をつけるとかなかなか分かりにくい。
簡単にJSのテストをする時に、viewファイルに以下のタグを書くこともあると思いますが、できればビューヘルパーを使いたいですよね。

sample.html.erb
<script>
</script>

実際のコード

以下のように書くと同じ事になります。

sample.html.erb
<%= javascript_tag do %>
<% end %>

ちなみにクラスはこうやって付けられます、
でも多分(class: "hoge")とかでも大丈夫だしこっちの方がスマート。
記事書いている今試せないのでちょっとあやふやです。

sample.html.erb
<%= javascript_tag(:class => :hoge) do %>
<% end %>

最後に

まあテストだけならビューヘルパー使わなくてもいいじゃないかという話かもしれませんが、
気分的に気になる人はこの書き方を使ってみては?

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

Ruby on Raildで空き家を貸し借り出来るwebアプリを開発

はじめに

Ruby on Railsを使って、空き家を貸し借りできるwebアプリを開発しました。ただし、正式に公開する前提で開発はしておらず、あくまでデモとして開発しました。

開発した目的としては、rails開発の訓練のためと、ポートフォリオを作成したかったためです。

この記事ではそのwebアプリの解説をしております。

注意点

1.このwebアプリはRuby on Railsチュートリアルで公開されているソースコードをベースに開発しました。

2.サーバーサイドの実装の訓練が目的で開発をしたため、フロントエンド関連はほとんど手を入れておりません。

ソースコード

ソースコードはgithubで公開しております。
github:null_house

参考にさせていただいた記事

Ruby on Railsチュートリアル
rails 予約機能をつける。(一回目)
Railsでコメント機能をつくってみよう
Railsで「投稿お気に入り追加機能」を実装する
railsで検索フォームを作ろう!!

大変お世話になりました。ありがとうございます。

HOME画面

ログインしてない場合のHome画面です
スクリーンショット 2019-07-28 15.20.01.png

サインアップ画面です
スクリーンショット 2019-07-28 15.23.46.png

届いたメールのURLをクリックし、アカウント有効化をすると自身のプロフィール画面にジャンプします。
スクリーンショット 2019-07-28 15.52.58.png

空き家を貸し出す

空き家を貸出しします
スクリーンショット 2019-07-28 15.55.45.png

作成すると、自身のプロフィールページにある「あなたが貸しに出してる空き家」の箇所に作成した空き家のタイトル名が表示されます。

スクリーンショット 2019-07-28 15.56.22.png

空き家の詳細ページ

以下は作成された空き家の詳細ページになります。
1.空き家のタイトル
2.空き家の説明文
3.空き家の貸出者
4.お気に入り機能
5.空き家へのコメント
6.空き家の内覧予約
7.空き家の適正価格算出
8.空き家の借用に対してされたオファーの内容(後述)

スクリーンショット 2019-07-28 16.18.09.png

様々な機能

  • お気に入りボタンを押すと、その空き家がお気に入りに登録されます。
  • コメント欄に文章を入力すると、コメントが表示されます。
  • 内覧予約の開始時間と終了時間に日時を入力すると、予約が表示されます。 スクリーンショット 2019-07-28 16.22.43.png

自身のプロフィールページにある「あなたがお気に入りに追加した空き家」の欄に、お気に入りに登録した空き家のタイトルが表示されます。

スクリーンショット 2019-07-28 16.23.17.png

家賃適正価格予測機能

回帰分析を用いて、空き家作成時に入力したサイズなどの値を元に、家賃の適正価格を表示します。

(モデル作成ソース)
import numpy as np 
import pandas as pd 
from sklearn import linear_model

##データセットはランダムに作成
data = {'layout' : [2, 1, 3, 4, 5], 
        'land_area' : [40, 20, 30, 55, 65],
        'building_area' : [50, 20, 35, 60, 70],
        'age_of_a_building' : [40, 50, 30,20,10],
        'adoress' : [7, 3, 8, 4, 1],
        'price' : [48000, 29000, 33000,55000,67000]}

df = pd.DataFrame(data)

Y_train = df['price']
X_train = df.drop("price", axis=1)

lm_model = linear_model.LinearRegression()
lm_model.fit(X_train,Y_train)

print("切片 =",lm_model.intercept_)
print("回帰係数 =",lm_model.coef_)
切片 = 27073.62274689006
回帰係数 = [    4.82355928 -1097.43589744  1625.64102564   -48.23559279
 -2076.92307692]
def lm(x1,x2,x3,x4,x5)
   (27073 + 4.82355928*x1 - 1097.43589744*x2 + 1625.64102564*x3  - 48.23559279*x4 - 2076.92307692*x5).to_i
  end
<% if current_user.id == @house.user_id %>
    <h3>家賃予測価格</h3>
    <p>この空き家の1ヶ月の適正価格は<%= lm(x1,x2,x3,x4,x5) %>円です</p>
  <% end %>

プレミアム会員機能

プレミアム会員のユーザーに対して、特定の表示を行います。
スクリーンショット 2019-07-28 16.32.37.png

<% if @user.premium? %>
    <h4>あなたはプレミアム会員です!!</h4>
  <% end %>

オファーを出す

空き家を借りたいときに、オファーを出すフォーム画面です。
スクリーンショット 2019-07-28 16.56.06.png

オファーを出すと、出された側の空き家ページにオファーの内容が表示されます。

<h3>オファー一覧</h3>
  <% if @offer %>
    user id : <%= @offer.user_id%><%= @offer.use %>
  <% end %>

スクリーンショット 2019-07-28 17.04.06.png

管理画面

管理者のみがアクセスできる管理画面を作成しました。

def ensure_correct_user
    if current_user.admin?
      #何もしない
    else
      flash[:notice] = "アクセス権限がありません"
      redirect_to root_url
    end
  end

ユーザー一覧

スクリーンショット 2019-07-28 16.35.16.png

コメント一覧

スクリーンショット 2019-07-28 16.47.11.png

内覧予約一覧

スクリーンショット 2019-07-28 16.50.28.png

データ視覚化画面

スクリーンショット 2019-07-28 16.52.33.png

 運営からのお知らせ機能

管理画面と同じく、管理者のみがアクセスできるページで、お知らせを作成したりできます

作成

スクリーンショット 2019-07-28 17.08.59.png

一覧

スクリーンショット 2019-07-28 17.10.08.png

詳細

スクリーンショット 2019-07-28 17.10.50.png

今後の課題

  • テストがおざなりになってしまったので、テストを書く訓練を今後したい。
  • 大規模な機械学習モデルを使用できるようにしたい(GCPにモデルをデプロイして予測を取得したり等)

最後に

webアプリ開発において、基本的な機能は実装できるようになったとは思います。
今後実践の機会を増やして、レベルアップしていきたいです。

※コメントやツッコミあればどんどんください!

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

線形回帰モデルによる価格予測機能を組み込んだ空き家貸し借りアプリをRuby on Railsで開発

はじめに

Ruby on Railsを使って、空き家を貸し借りできるwebアプリを開発しました。ただし、正式に公開する前提で開発はしておらず、あくまでデモとして開発しました。

開発した目的としては、rails開発の訓練のためと、ポートフォリオを作成したかったためです。

この記事ではそのwebアプリの解説をしております。

線形回帰モデルによる価格予測の実装については中盤辺りに記載してます。

注意点

1.このwebアプリはRuby on Railsチュートリアルで公開されているソースコードをベースに開発しました。

2.サーバーサイドの実装の訓練が目的で開発をしたため、フロントエンド関連はほとんど手を入れておりません。

ソースコード

ソースコードはgithubで公開しております。
github:null_house

参考にさせていただいた記事

Ruby on Railsチュートリアル
rails 予約機能をつける。(一回目)
Railsでコメント機能をつくってみよう
Railsで「投稿お気に入り追加機能」を実装する
railsで検索フォームを作ろう!!

大変お世話になりました。ありがとうございます。

HOME画面

ログインしてない場合のHome画面です
スクリーンショット 2019-07-28 15.20.01.png

サインアップ画面です
スクリーンショット 2019-07-28 15.23.46.png

届いたメールのURLをクリックし、アカウント有効化をすると自身のプロフィール画面にジャンプします。
スクリーンショット 2019-07-28 15.52.58.png

空き家を貸し出す

空き家を貸出しします
スクリーンショット 2019-07-28 15.55.45.png

作成すると、自身のプロフィールページにある「あなたが貸しに出してる空き家」の箇所に作成した空き家のタイトル名が表示されます。

スクリーンショット 2019-07-28 15.56.22.png

空き家の詳細ページ

以下は作成された空き家の詳細ページになります。
1.空き家のタイトル
2.空き家の説明文
3.空き家の貸出者
4.お気に入り機能
5.空き家へのコメント
6.空き家の内覧予約
7.空き家の適正価格算出
8.空き家の借用に対してされたオファーの内容(後述)

スクリーンショット 2019-07-28 16.18.09.png

様々な機能

  • お気に入りボタンを押すと、その空き家がお気に入りに登録されます。
  • コメント欄に文章を入力すると、コメントが表示されます。
  • 内覧予約の開始時間と終了時間に日時を入力すると、予約が表示されます。 スクリーンショット 2019-07-28 16.22.43.png

自身のプロフィールページにある「あなたがお気に入りに追加した空き家」の欄に、お気に入りに登録した空き家のタイトルが表示されます。

スクリーンショット 2019-07-28 16.23.17.png

家賃適正価格予測機能

回帰分析を用いて、空き家作成時に入力したサイズなどの値を元に、家賃の適正価格を表示します。

(モデル作成ソース)
import numpy as np 
import pandas as pd 
from sklearn import linear_model

##データセットはランダムに作成
data = {'layout' : [2, 1, 3, 4, 5], 
        'land_area' : [40, 20, 30, 55, 65],
        'building_area' : [50, 20, 35, 60, 70],
        'age_of_a_building' : [40, 50, 30,20,10],
        'adoress' : [7, 3, 8, 4, 1],
        'price' : [48000, 29000, 33000,55000,67000]}

df = pd.DataFrame(data)

Y_train = df['price']
X_train = df.drop("price", axis=1)

lm_model = linear_model.LinearRegression()
lm_model.fit(X_train,Y_train)

print("切片 =",lm_model.intercept_)
print("回帰係数 =",lm_model.coef_)
切片 = 27073.62274689006
回帰係数 = [    4.82355928 -1097.43589744  1625.64102564   -48.23559279
 -2076.92307692]
def lm(x1,x2,x3,x4,x5)
   (27073 + 4.82355928*x1 - 1097.43589744*x2 + 1625.64102564*x3  - 48.23559279*x4 - 2076.92307692*x5).to_i
  end
<% if current_user.id == @house.user_id %>
    <h3>家賃予測価格</h3>
    <p>この空き家の1ヶ月の適正価格は<%= lm(x1,x2,x3,x4,x5) %>円です</p>
  <% end %>

プレミアム会員機能

プレミアム会員のユーザーに対して、特定の表示を行います。
スクリーンショット 2019-07-28 16.32.37.png

<% if @user.premium? %>
    <h4>あなたはプレミアム会員です!!</h4>
  <% end %>

オファーを出す

空き家を借りたいときに、オファーを出すフォーム画面です。
スクリーンショット 2019-07-28 16.56.06.png

オファーを出すと、出された側の空き家ページにオファーの内容が表示されます。

<h3>オファー一覧</h3>
  <% if @offer %>
    user id : <%= @offer.user_id%><%= @offer.use %>
  <% end %>

スクリーンショット 2019-07-28 17.04.06.png

管理画面

管理者のみがアクセスできる管理画面を作成しました。

def ensure_correct_user
    if current_user.admin?
      #何もしない
    else
      flash[:notice] = "アクセス権限がありません"
      redirect_to root_url
    end
  end

ユーザー一覧

スクリーンショット 2019-07-28 16.35.16.png

コメント一覧

スクリーンショット 2019-07-28 16.47.11.png

内覧予約一覧

スクリーンショット 2019-07-28 16.50.28.png

データ視覚化画面

スクリーンショット 2019-07-28 16.52.33.png

 運営からのお知らせ機能

管理画面と同じく、管理者のみがアクセスできるページで、お知らせを作成したりできます

作成

スクリーンショット 2019-07-28 17.08.59.png

一覧

スクリーンショット 2019-07-28 17.10.08.png

詳細

スクリーンショット 2019-07-28 17.10.50.png

今後の課題

  • テストがおざなりになってしまったので、テストを書く訓練を今後したい。
  • 大規模な機械学習モデルを使用できるようにしたい(GCPにモデルをデプロイして予測を取得したり等)

最後に

webアプリ開発において、基本的な機能は実装できるようになったとは思います。
今後実践の機会を増やして、レベルアップしていきたいです。

※コメントやツッコミあればどんどんください!

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

マイグレーションファイルの記述を間違えてしまった

本日の学習のハマってしまったエラー内容の備忘録です。

マイグレーションファイルの記述を間違えてしまった時の対処法

本日の学習中に
bundle exec rake db:seedを実行したいのにエラーが出てしまい
原因を探していたらマイグレーションファイルの記述ミスでした。
書き直して上書き保存してもエラーになっていましたので
ターミナルから変更するのだろうかと考え、調べていました。

今日作っていたのは下記のコード

class CreateItems < ActiveRecord::Migration[5.2]
  def change
    create_table :items do |t|
      t.string :items <= nameに変更
      t.integer :price
      t.integer :user_id
      t.timestamps
    end
  end
end

上記のコードのitemsnameに変更した後に

bundle exec rake db:migrate:reset

マイグレーションファイルをリセットして再度マイグレーションを実行します。
※自分は実行しましたが、必要ないかも?

bundle exec rake db:migrate

このやり方で一応は成功しました。

調べていたら
rake db:rollback
などもあるとの事なので今度テスト環境で試してみたいです。

参考:http://railsdoc.com/references/rake%20db:migrate

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

プログラミン未経験者はRailsチュートリアを1周目はじっくりやってはいけない

想定読者

・プログラミン初心者
・実務未経験者
・これからRailsチュートリアをやろうとしている方

著者情報と記事の概要

著者情報

・HTML, CSS, JavaScriptを少しかじった程度の初心者
・ProgateでRubyおよびRialsを1周やっている
・Railsで転職活動用のWebアプリを作りたい人

記事の概要

Ruby on Railsを学習すると決めたからには、誰もがやるであろうRailsチュートリアです。Twitterで検索すると多くのプログラミン初学者が挑戦しているのを見かけます(私もその一人)。
では、プログラミング初心者の私がRailsチュートリアをやることで、どのレベルにまで到達できたのか、紹介したいと思います。

理解度30%

このQiitaは、実はRailsチュートリア走破後すぐに書いているのですが、率直な感想として、Railsチュートリアの30%程しか理解できなかったと感じています。ここでいう「理解」は、どれだけ再現できるかだと思ってください。とくにテストの書き方については理解度はかなり低めです。ちなみに私は、Railsチュートリアを25日間でやりました。時間でいうと、100時間ほどです。割とじっくりやっと方ではないかと思います。

各章ごとの理解度

内容 理解度 コメント
1章 環境設定 100%
2章 MVCモデル概要 100%
3章 ルーティング 100%
4章 Ruby文法 100% Progateをやっていれば大丈夫
5章 Viewを書く 100%
6章 モデルを書く 50% バリデーション等の流れをフォロー難
7章 ユーザー登録機能 60%
8章 ログイン機構 50%
9章 ログインしたまま機構 3% 脳内がトークンで埋め尽くされる
10章 ユーザーのREST 50% 細かな機能が多くなってくる
11章 ユーザー有効化 50% メール関係のテストが複雑
12章 パスワード再設定 30% 似たような機構が多く、混同してくる
13章 マイクロポスト機構 20% まったく全体像が見えない
14章 フォロー機構 20% relationshipモデルが難しい

ちなみに、最も難しい9章はスキップしてもいいと本書でも明言されています。

身についたとは口が裂けても言えない

Railsチュートリアル1週目を終えて、さくさくWebアプリを作れるようになったかというと、まったくそんなことはありません。一部のブログ等では、「Railsチュートリアルを1周すればWebアプリをさくさく作れる一歩手前のレベルになります」と書いてた気がしますが、私の場合は全然そんなことありませんでした。

1周目は概念の習得に専念

正直、Railsチュートリアルで紹介のあった細かい技術はすでに大方覚えていません(笑) 「プログラミンは暗記ではない!」というありがたい言葉もありますが、流石に「こんなメソッドあったな〜」と後からググり治せるくらいの記憶の残滓は必要なはずですが、残ってません。一方で、MVCモデルや、Railsの大方の挙動、フォルダ構成はかなり理解できました。ただ、概念を習得するのに25日間、100時間の投入が必要だったか?と問われると、まったく必要なかったと思います。

結論:1周目はスピーディーに。現場Rails等を併用して。

結論として、Railsチュートリアルの1周目は、理解度よりもスピードを意識したほうが得策であると考えます。(じっくりやっても結局覚えれません)。結局のところ自分の頭で1から考えるしか大きな成長は望めませんので、Railsチュートリアルはそこそこに、とっとと自作のWebアプリを作り始めるのが得策だと感じました。
あと、もう一点。10章をやっている時に、これまたRails学習の決定版である『現場で使える Ruby on Rails 5速習実践ガイド』、通称『現場Rails』をパラパラとやっていたのですが、日本人著者の利点といいましょうか。併用すると、Railsチュートリアルのもやもやが晴れるところもあり、並行してすすめるのは結構おすすめです!
download.jpg

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

プログラミング未経験者はRailsチュートリア1周目をじっくりやってはいけない

想定読者

・プログラミン初心者
・実務未経験者
・これからRailsチュートリアをやろうとしている方

著者情報と記事の概要

著者情報

・HTML, CSS, JavaScriptを少しかじった程度の初心者
・ProgateでRubyおよびRialsを1周やっている
・Railsで転職活動用のWebアプリを作りたい人

記事の概要

Ruby on Railsを学習すると決めたからには、誰もがやるであろうRailsチュートリアです。Twitterで検索すると多くのプログラミン初学者が挑戦しているのを見かけます(私もその一人)。
では、プログラミング初心者の私がRailsチュートリアをやることで、どのレベルにまで到達できたのか、紹介したいと思います。

理解度30%

このQiitaは、実はRailsチュートリア走破後すぐに書いているのですが、率直な感想として、Railsチュートリアの30%程しか理解できなかったと感じています。ここでいう「理解」は、どれだけ再現できるかだと思ってください。とくにテストの書き方については理解度はかなり低めです。ちなみに私は、Railsチュートリアを25日間でやりました。時間でいうと、100時間ほどです。割とじっくりやっと方ではないかと思います。

各章ごとの理解度

内容 理解度 コメント
1章 環境設定 100%
2章 MVCモデル概要 100%
3章 ルーティング 100%
4章 Ruby文法 100% Progateをやっていれば大丈夫
5章 Viewを書く 100%
6章 モデルを書く 50% バリデーション等の流れをフォロー難
7章 ユーザー登録機能 60%
8章 ログイン機構 50%
9章 ログインしたまま機構 3% 脳内がトークンで埋め尽くされる
10章 ユーザーのREST 50% 細かな機能が多くなってくる
11章 ユーザー有効化 50% メール関係のテストが複雑
12章 パスワード再設定 30% 似たような機構が多く、混同してくる
13章 マイクロポスト機構 20% まったく全体像が見えない
14章 フォロー機構 20% relationshipモデルが難しい

ちなみに、最も難しい9章はスキップしてもいいと本書でも明言されています。

身についたとは口が裂けても言えない

Railsチュートリアル1週目を終えて、さくさくWebアプリを作れるようになったかというと、まったくそんなことはありません。一部のブログ等では、「Railsチュートリアルを1周すればWebアプリをさくさく作れる一歩手前のレベルになります」と書いてた気がしますが、私の場合は全然そんなことありませんでした。

1周目は概念の習得に専念

正直、Railsチュートリアルで紹介のあった細かい技術はすでに大方覚えていません(笑) 「プログラミンは暗記ではない!」というありがたい言葉もありますが、流石に「こんなメソッドあったな〜」と後からググり治せるくらいの記憶の残滓は必要なはずですが、残ってません。一方で、MVCモデルや、Railsの大方の挙動、フォルダ構成はかなり理解できました。ただ、概念を習得するのに25日間、100時間の投入が必要だったか?と問われると、まったく必要なかったと思います。

結論:1周目はスピーディーに。現場Rails等を併用して。

結論として、Railsチュートリアルの1周目は、理解度よりもスピードを意識したほうが得策であると考えます。(じっくりやっても結局覚えれません)。結局のところ自分の頭で1から考えるしか大きな成長は望めませんので、Railsチュートリアルはそこそこに、とっとと自作のWebアプリを作り始めるのが得策だと感じました。
あと、もう一点。10章をやっている時に、これまたRails学習の決定版である『現場で使える Ruby on Rails 5速習実践ガイド』、通称『現場Rails』をパラパラとやっていたのですが、日本人著者の利点といいましょうか。併用すると、Railsチュートリアルのもやもやが晴れるところもあり、並行してすすめるのは結構おすすめです!

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

【Rails】LINEログインでemailを取得する

こんにちは!本日はLINEログイン時にemailを取得する方法をお伝えします!
facebookは取得できるのに、LINEはgemのデフォルトで取ってこれていないようでした。
そこで必要な部分をオーバーライドして使います!

(【注意】本記事はomniauth-lineのプラグインを利用しています)

【目次】

  1. LINE delvelopersでemail取得申請をする
  2. omniauth-lineをオーバーライドしてid_tokenのペイロードからemailを取得する

1.LINE delvelopersでemail取得申請をする

やることは簡単で、LINE側にLINEログインの時にemail情報使わせてお願い!と申請するだけです。
(その際に、ユーザーに許可を求めている文言等の確認を送る、そして待つ(1日くらい?))

↓こちらに関しては公式ドキュメント・他の方々の記事が参考になります。↓
メールアドレス取得権限を申請する(公式)
RailsでOpenID connectを用いたLINEログイン

2.omniauth-lineをオーバーライドしてid_tokenからemailを取得する

ここからが出番です!

【結論】以下のようにomniauth-linegemのomniauth/strategies/line.rbオーバーライドするだけです。

# app/config/initializers/omniauth/strategies/line.rb

require 'omniauth-oauth2'

module OmniAuth
  module Strategies
    class Line < OmniAuth::Strategies::OAuth2
      option :scope, 'email profile openid' #追記

      info do
        {
          name:        raw_info['displayName'],
          image:       raw_info['pictureUrl'],
          description: raw_info['statusMessage'],
          email:    JWT.decode(access_token.params['id_token'], ENV['LINE_CHANNEL_SECRET']).first['email'] #追記
        }
      end
    end
  end
end

option :scope→ユーザーが付与する権限
option :scopeemailを追加
(ユーザーが付与する権限にemailもどうぞ使ってください!といった感じですね。)

実際に確認して見ます(以下の情報は加工してあります)。

# access_token.params['id_token']

<OmniAuth::AuthHash 
credentials=
  #<OmniAuth::AuthHash 
    expires=true 
    expires_at=1566893036 
    refresh_token="sAuxKnUCETKgOYs2ll7J" 
    token="eyJhbGciOiJIUzI1NiJ9.Hj3glrwEWDOPWZNnTLjqsdy2l78TgLlZYVX3AurU6dPhenV4u_G5sZ_aqVWfwmVWcvYlF_Rhf-    6D_BoRqMY66lMAa0sb63mvsma1d_av1JZgl8TrpN9WLi4p7UfWxv3QvM8lEQn0qZrQeU3Vp88Kv097jRKgdC7Z364TgZZD6OU.CEouR6pTTLEHp..."> 
    extra=#<OmniAuth::AuthHash> 
    info=#<OmniAuth::AuthHash::InfoHash description=nil 
    email="eyJ0eXAiOiJKV1QiLCJhbGciOdcdscsdciJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FjY2Vzcy5saW5lLm1lIiwic3ViIjoiVTNlcdcsNGJkNjYyOTU4NjIzMTVkYmRmODVmNGMxMDY1M2Y2IiwiYXVkIjoiMTYxNjIyOTMxNCIsImV4cCI6M..." 
    image="https://profile.line-scdn.net/0hk1oz7EfkNB5cdc...
    name="example"> 
provider="line" 
uid="U3e4bd6629586..."
>

OmniAuth::AuthHash(id_tokenのペイロード)にemail情報が含まれていますね!
しかし、現状だとわからないのでJWTでデコードします。

ENV['LINE_CHANNEL_SECRET']はLINEのChannel Secretのことです。

JWT.decode(access_token.params['id_token'], ENV['LINE_CHANNEL_SECRET'])

すると以下のように

<OmniAuth::AuthHash 
  credentials=#<OmniAuth::AuthHash 
  expires=true 
  expires_at=1566894068 
  refresh_token="UvrpsvdMgwNYHLC4LO9j" 
  token="eyJhbGciOiJIUzI1NiJ9.Cl3zWMKrL6g59qIbWWk0nhdfhNLFB7NNZiSIoHI8zKq89BW77wIx-jregc-jxfENUYwTjZkNwK0lYlz7c4ZRTOcF2iWJK3zl1aTKroyF9lfNj1aThmBeyVioNvbbfSIjLmdpK2jjBh1E2j59D_twbHWTFWiAwuI7qokOp_z8lbI.G8QUoudL46KbuyqhN7UpvBa1PenH6MDQvNPrD_tGz-8"> 
  extra=#<OmniAuth::AuthHash> 
  info=#<OmniAuth::AuthHash::InfoHash 
  description=nil 
  email=[#<OmniAuth::AuthHash amr=#<Hashie::Array ["linesso"]> aud="1616229314" 
  # emailが認識できるようになる!
  email="example@gmail.com" 
  exp=1564305669 
  iat=1564302069 
  
  
  

email情報が認識できる形になりましたね!
JSON Web Tokens - jwt.ioでもペイロードさえ入力すればデコードしてくれるのでJWTが何をしているかよくわからない方はおすすめです。

これでemailの取得は完了です。request.env['omniauth.auth']からでもemailからでも取得できるので
formのvalueに入れて初期値っぽくしてしまいましょう!

p request.env['omniauth.auth'][email]

> example@gmail.com

おまけ

副業案件お待ちしております。得意分野はRuby on Railsです!
職務経歴書

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

RailsへのBootstrap導入方法

RailsへのBootstrap導入方法 備忘録

1. Gemfile

Gemfileに以下4点を追加する

gem "sass-rails", "~>5.0"
gem "bootstrap-sass", "~>3.3.6"
gem "jquery-rails"
gem "jquery-ui-rails"

スクリーンショット 2019-07-28 17.18.13.png

2. bundle update

ターミナルにて bundle update を実行


3. csccファイル変更

ファイル名変更
/app/assets/stylesheets/application.css から
/app/assets/stylesheets/application.scss

末に以下2点を追加

@import "bootstrap-sprockets";
@import "bootstrap";

スクリーンショット 2019-07-28 17.39.06.png

4. jsファイル変更

/app/assets/javascripts/application.js

以下2点を追加(図では13、18行目)

//= require jquery
//= require bootstrap-sprockets

スクリーンショット 2019-07-28 17.48.01.png

実際に使ってみる

https://getbootstrap.com/
今回はGemfileにてversion3系を指定したので、v3.3.7 を選択

スクリーンショット 2019-07-28 19.02.42.png

使いたいデザインを探して…

スクリーンショット 2019-07-28 19.22.35.png

htmlにclassを適用する

スクリーンショット 2019-07-28 19.19.47.png

スクリーンショット 2019-07-28 19.24.13.png

無事にデザインが反映されました

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

DockerのみでつくるRailsプロジェクト

概要

Railsプロジェクトを作成する際にDocker環境があるのにわざわざローカル環境にRubyを入れて…は効率が悪すぎるので、Rubyコンテナを使用してRailsプロジェクトを作成しました。
忘備録に手順をまとめたので同じようにRailsプロジェクトを作成したい方の助けになればいいと思います。

環境

  • macOS Mojave 10.14
  • Docker version 18.09.2, build 6247962

rubyコンテナの取得

DockerHubよりRubyイメージを取得

$ docker pull ruby:2.6.3
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ruby 2.6.3 f1c13927d193 13 days ago 870MB

Gemfileの生成

Rubyイメージからコンテナを作成し、Gemfileを生成する。

// コンテナの起動
$ docker run --rm -v `pwd`:/myapp -w /myapp -it ruby:2.6.3 bash
// オプション
--rm: 実行後のコンテナを削除
-v:   共有ディレクトリの設定
-w:   ワーキングディレクトリの設定
-it:  コンテナをフォアグラウンドで実行

// Gemfileの生成
root@a1eeb5367697:/myapp# bundle init
Writing new Gemfile to /myapp/Gemfile

Gemfileの編集

Gemfileを編集し、Railsをインストールするように変更する。

Gemfile
- # gem "rails"
+ gem "rails"

Dockerfile、docker-compose.ymlの作成

コンテナを作成するDockerfileとdocker-compose.ymlを作成する。
各ファイルは下記のようにディレクトリを作成し、配置する。

ディレクトリ構成

├ docker-compose.yml
└ docker
  ├ mysql
  │  ├ volumes ← DB永続化用ディレクトリ
  │  └ password.env
  └ rails
     └ Dockerfile

ファイル内容

Dockerfile
FROM ruby:2.6.3
ENV LANG C.UTF-8

RUN set -x && \
    : "前提パッケージのインストール" && \
    apt-get update -qq && \
    apt-get install -y \
        build-essential \
        mysql-client \
        nodejs

RUN set -x && \
    : "パッケージのインストール" && \
    gem install bundler

WORKDIR /tmp
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock
RUN bundle install

ENV APP_HOME /myapp
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
ADD . $APP_HOME
docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7.26
    ports:
      - "3306:3306"
    volumes:
      - ./docker/mysql/volumes:/var/lib/mysql
    env_file: ./docker/mysql/password.env
  web:
    build:
      context: .
      dockerfile: ./docker/rails/Dockerfile
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    ports:
      - "3000:3000"
    volumes:
      - .:/myapp
    environment:
      RAILS_ENV: development
    env_file: ./docker/mysql/password.env
    depends_on:
      - db

DBのパスワードを設定

password.env
MYSQL_ROOT_PASSWORD=password

Railsプロジェクトの生成

Dockerfile、docker-compose.ymlで定義したコンテナのビルドを行い、コンテナにログインする。

// コンテナビルド
$ docker-compose build

// コンテナにログイン
$ docker-compose up -d
$ docker-compose exec web bash
// オプション
-d: バックグラウンド実行

// railsプロジェクトの生成
root@477e08f00455:/var/www/CleanManager# rails new . -d mysql -BT
// オプション
-d: DBをmysqlに設定
-B: bundle installを省略
-T: テストファイルの作成を省略

// パッケージのインストール
root@477e08f00455:/var/www/CleanManager# bundle install

railsアプリの起動

コンテナの再起動を行う。
docker-compose.ymlにビルドインサーバを起動するコマンドを記述しているため、別途起動するコマンドを実行する必要はない。
再起動後、ブラウザで http://localhost:3000 にアクセスし起動していることを確認する。

root@477e08f00455:/var/www/CleanManager# exit
$ docker-compose down
$ docker-compose up -d

まとめ

Docker初心者だったので、よくわからないことがありましたが、なんとか作成することができました。
DBをMySQLからPostgreSQLに変更したかったのですが、PostgreSQLコンテナが起動直後に落ちてしまったのでMySQLで妥協しました。
今後も原因を探して判明しだい、再度記事にしたいと思います。
間違い等ありましたら、コメントから修正お願いします。

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

【Rails】 RSpecでNoMethodErrorと言われた人へ

エラーの内容

ターミナルでbundle exec rspecを実行すると以下のような文が表示されてしまい、テストの判定が行われませんでした。

undefined method `build' for #<RSpec::ExampleGroups::User::Create:~~~~~>

undefined method `create' for #<RSpec::ExampleGroups::User::Create:~~~~~>

解決法

1. spec以下にフォルダを作成する

"spec/support/factory_bot.rb"を作成します。

2. factory_bot.rbに次の内容を記述する

factory_bot.rb
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end

3. rails_helper.rbで2のファイルを読み込む

rails_helper.rb
require 'support/factory_bot'

ここまで終えて、もう一度bundle exec rspecを実行したところ、無事にテストコードの判定を行うことができました。

参考

Stack Overflow「undefined method `build' for #<RSpec::ExampleGroups::UserName:>

以上を参考に解決することができました。ありがとうございます。

さいごに

前例探しに手間取ったので、記事として残します。同じエラーに遭遇した方の参考になれば幸いです。なお、理屈についてはいまの私では説明できないため、未来の私への覚書も兼ねています。

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

HomebrewでrbenvをmacOS Sierra 10.12にインストールする指定したバージョンのRubyをつかう

前提

Homebrewがインストールされていること

rbenvをインストールする

<コンピューター名>:~ <ユーザー名>$ brew install rbenv
==> Installing dependencies for rbenv: openssl, pkg-config and ruby-build
==> Installing rbenv dependency: openssl
==> Downloading https://homebrew.bintray.com/bottles/openssl-1.0.2s.sierra.bottl
==> Downloading from https://akamai.bintray.com/e5/e556bbb8902700cd3cb896e0635cc
######################################################################## 100.0%
==> Pouring openssl-1.0.2s.sierra.bottle.tar.gz
==> Caveats
A CA file has been bootstrapped using certificates from the SystemRoots
keychain. To add additional certificates (e.g. the certificates added in
the System keychain), place .pem files in
  /usr/local/etc/openssl/certs

and run
  /usr/local/opt/openssl/bin/c_rehash

openssl is keg-only, which means it was not symlinked into /usr/local,
because Apple has deprecated use of OpenSSL in favor of its own TLS and crypto libraries.

If you need to have openssl first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl/bin:$PATH"' >> ~/.bash_profile

For compilers to find openssl you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl/include"

For pkg-config to find openssl you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/openssl/lib/pkgconfig"

==> Summary
?  /usr/local/Cellar/openssl/1.0.2s: 1,795 files, 12.4MB
==> Installing rbenv dependency: pkg-config
==> Downloading https://homebrew.bintray.com/bottles/pkg-config-0.29.2.sierra.bo
==> Downloading from https://akamai.bintray.com/8e/8eb723bfc03cd468d779d54d015d4
######################################################################## 100.0%
==> Pouring pkg-config-0.29.2.sierra.bottle.tar.gz
Error: The `brew link` step did not complete successfully
The formula built, but is not symlinked into /usr/local
Could not symlink bin/pkg-config
Target /usr/local/bin/pkg-config
is a symlink belonging to pkg-config. You can unlink it:
  brew unlink pkg-config

To force the link and overwrite all conflicting files:
  brew link --overwrite pkg-config

To list all files that would be deleted:
  brew link --overwrite --dry-run pkg-config

Possible conflicting files are:
/usr/local/bin/pkg-config -> /usr/local/Cellar/pkg-config/0.28/bin/pkg-config
/usr/local/share/aclocal/pkg.m4 -> /usr/local/Cellar/pkg-config/0.28/share/aclocal/pkg.m4
/usr/local/share/doc/pkg-config/pkg-config-guide.html
/usr/local/share/doc/pkg-config/pkg-config-guide.html
/usr/local/share/man/man1/pkg-config.1 -> /usr/local/Cellar/pkg-config/0.28/share/man/man1/pkg-config.1
==> Summary
?  /usr/local/Cellar/pkg-config/0.29.2: 11 files, 627.1KB
==> Installing rbenv dependency: ruby-build
==> Downloading https://github.com/rbenv/ruby-build/archive/v20190615.tar.gz
==> Downloading from https://codeload.github.com/rbenv/ruby-build/tar.gz/v201906
######################################################################## 100.0%
==> ./install.sh
?  /usr/local/Cellar/ruby-build/20190615: 448 files, 225.5KB, built in 5 seconds
==> Installing rbenv
==> Downloading https://homebrew.bintray.com/bottles/rbenv-1.1.2.sierra.bottle.t
######################################################################## 100.0%
==> Pouring rbenv-1.1.2.sierra.bottle.tar.gz
?  /usr/local/Cellar/rbenv/1.1.2: 36 files, 65.1KB
==> Caveats
==> openssl
A CA file has been bootstrapped using certificates from the SystemRoots
keychain. To add additional certificates (e.g. the certificates added in
the System keychain), place .pem files in
  /usr/local/etc/openssl/certs

and run
  /usr/local/opt/openssl/bin/c_rehash

openssl is keg-only, which means it was not symlinked into /usr/local,
because Apple has deprecated use of OpenSSL in favor of its own TLS and crypto libraries.

If you need to have openssl first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl/bin:$PATH"' >> ~/.bash_profile

For compilers to find openssl you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl/include"

For pkg-config to find openssl you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/openssl/lib/pkgconfig"

rbenvのバージョンを確認する

<コンピューター名>:~ <ユーザー名>$ rbenv -v
rbenv 1.1.2

インストールできるRubyのバージョンを確認する

<コンピューター名>:~ <ユーザー名>$ rbenv install -l
Available versions:
  (省略)
  2.6.0-dev
  2.6.0-preview1
  2.6.0-preview2
  2.6.0-preview3
  2.6.0-rc1
  2.6.0-rc2
  2.6.0
  2.6.1
  2.6.2
  2.6.3
  (省略)

バージョン2.6.3をインストールする

rbenv install 2.6.3

インストールされているバージョンを確認する

<コンピューター名>:~ <ユーザー名>$ rbenv versions
* system (set by /Users/<ユーザー名>/.rbenv/version)
  2.6.3

rbenvで使用するRubyのバージョンを2.6.3にする

<コンピューター名>:~ <ユーザー名>$ rbenv global 2.6.3

rbenvで使用するRubyのバージョンを確認する

<コンピューター名>:~ <ユーザー名>$ rbenv versions
  system
* 2.6.3 (set by /Users/<ユーザー名>/.rbenv/version)

rubyコマンドからバージョンを確認する

<コンピューター名>:~ <ユーザー名>$ ruby -v
ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin16]

MacにデフォルトでインストールされているRubyのバージョンになってしまっている

実行されているRubyの場所を確認する

<コンピューター名>:~ <ユーザー名>$ which ruby
/usr/bin/ruby

MacにデフォルトでインストールされているRubyの場所になってしまっている

.bash_profileを開く

<コンピューター名>:~ <ユーザー名>$ open .bash_profile

以下を追記する

eval "$(rbenv init -)"

以下でbash_profileを実行する

<コンピューター名>:~ <ユーザー名>$ source ./bash_profile

実行されているRubyの場所を確認する

<コンピューター名>:~ <ユーザー名>$ which ruby
/Users/<ユーザー名>/.rbenv/shims/ruby

homebrewでインストールしたRubyの場所が表示される

rubyコマンドからバージョンを確認する

<コンピューター名>:~ <ユーザー名>$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin16]

homebrewでインストールしたRubyのバージョンが表示される

疑問

結局パスの設定をする必要はないのか?

参考

https://weblabo.oscasierra.net/ruby-install-rbenv-homebrew-macos/
https://easyramble.com/rbenv-ruby-version-trouble.html
https://www.grami-sensei.com/ruby/0/1/4
https://weblabo.oscasierra.net/ruby-install-rbenv-homebrew-macos/

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

【Rails】エラーメッセージをviewに表示する仕組み。errors.any? を使って

バージョン

Rails 5.0.7.2

はじめに

Railsのモデル(model)でバリデーション(Validation)エラーが発生した場合に、対応するビュー(view)にエラー内容を表示する。

エラー出力のイメージ

スクリーンショット 2019-07-28 15.45.18.png

Nameタブが空欄のままCreate Groupした時にエラー出力
スクリーンショット 2019-07-28 15.45.00.png

処理の流れ

1.モデル(model)クラスにバリデーション(Validation)を設定

validatesメソッドについて

validates(検証するフィールド名, :length => 検証パラメータ)

以下のモデル(model)ではvalidatesメソッドの第一引数にgroupTBLのnameカラムを設定。
第二引数以降に検証パラメータを設定している。この場合、

1.上記スナップショットのnameフォームが空欄であることを非許容
2.nameフォームは本アプリケーションないで一意になること

となる

group.rb
class Group < ApplicationRecord
    has_many :members
    has_many :users, through: :members
    has_many :messages
    validates :name, presence: true, uniqueness: true
end

2.コントローラ(controller)に条件分岐を設定する

saveに成功したらrootに設定したビュー(view)にリダイレクト
savaに失敗したらrenderでedit.html.hamlに再度遷移させる

groups_controller.rb
#必要な箇所だけ抜粋

class GroupsController < ApplicationController

    def create
      @group = Group.new(group_params)
      if @group.save
        redirect_to root_path, notice: 'グループを作成しました'
      else
        render :new
      end
    end


3.ビュー(view)にエラー出力を定義

:point_up:hamlで書いています。

ビューのエラー文を表示したい箇所に「if 〇〇.errors.any?」を記載する。

モデル(model)でバリデーションエラーが発生した場合、modelのerrorsにエラーメッセージが設定さる。さらに、full_messagesでバリデーションのエラーメッセージを配列で取得できる。

@group.errors.full_messagesでエラーを全て表示。
@group.errors.full_messages.countで件数を表示できる。

edit.html.haml
#必要な箇所だけ抜粋
.chat-group-form
  %h1 新規チャットグループ

  = form_for @group do |f|
    - if @group.errors.any?
      .chat-group-form__errors
        %h2= "#{@group.errors.full_messages.count}件のエラーが発生しました。"
        %ul
          - @group.errors.full_messages.each do |message|
            %li= message
    .chat-group-form__field
      .chat-group-form__field--left
        = f.label :name, class: 'chat-group-form__label'
      .chat-group-form__field--right
        = f.text_field :name, class: 'chat__group_name chat-group-form__input', placeholder: 'グループ名を入力してください'

以上ざくっと。

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

【Rails】エラーメッセージをviewに表示する仕組み errors.any?

バージョン

Rails 5.0.7.2

はじめに

Railsのモデル(model)でバリデーション(Validation)エラーが発生した場合に、対応するビュー(view)にエラー内容を表示する。

エラー出力のイメージ

スクリーンショット 2019-07-28 15.45.18.png

Nameタブが空欄のままCreate Groupした時にエラー出力
スクリーンショット 2019-07-28 15.45.00.png

処理の流れ

1.モデル(model)クラスにバリデーション(Validation)を設定

validatesメソッドについて

validates(検証するフィールド名, :length => 検証パラメータ)

以下のモデル(model)ではvalidatesメソッドの第一引数にgroupTBLのnameカラムを設定。
第二引数以降に検証パラメータを設定している。この場合、

1.上記スナップショットのnameフォームが空欄であることを非許容
2.nameフォームは本アプリケーションないで一意になること

となる

group.rb
class Group < ApplicationRecord
    has_many :members
    has_many :users, through: :members
    has_many :messages
    validates :name, presence: true, uniqueness: true
end

2.コントローラ(controller)に条件分岐を設定する

saveに成功したらrootに設定したビュー(view)にリダイレクト
savaに失敗したらrenderでedit.html.hamlに再度遷移させる

groups_controller.rb
#必要な箇所だけ抜粋

class GroupsController < ApplicationController

    def create
      @group = Group.new(group_params)
      if @group.save
        redirect_to root_path, notice: 'グループを作成しました'
      else
        render :new
      end
    end


3.ビュー(view)にエラー出力を定義

:point_up:hamlで書いています。

ビューのエラー文を表示したい箇所に「if 〇〇.errors.any?」を記載する。

モデル(model)でバリデーションエラーが発生した場合、modelのerrorsにエラーメッセージが設定さる。さらに、full_messagesでバリデーションのエラーメッセージを配列で取得できる。

@group.errors.full_messagesでエラーを全て表示。
@group.errors.full_messages.countで件数を表示できる。

edit.html.haml
#必要な箇所だけ抜粋
.chat-group-form
  %h1 新規チャットグループ

  = form_for @group do |f|
    - if @group.errors.any?
      .chat-group-form__errors
        %h2= "#{@group.errors.full_messages.count}件のエラーが発生しました。"
        %ul
          - @group.errors.full_messages.each do |message|
            %li= message
    .chat-group-form__field
      .chat-group-form__field--left
        = f.label :name, class: 'chat-group-form__label'
      .chat-group-form__field--right
        = f.text_field :name, class: 'chat__group_name chat-group-form__input', placeholder: 'グループ名を入力してください'

以上ざくっと。

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

【Rails】エラーメッセージをviewに表示する仕組み。 〜errors.any?〜を使って

バージョン

Rails 5.0.7.2

はじめに

Railsのモデル(model)でバリデーション(Validation)エラーが発生した場合に、対応するビュー(view)にエラー内容を表示する。

エラー出力のイメージ

スクリーンショット 2019-07-28 15.45.18.png

Nameタブが空欄のままCreate Groupした時にエラー出力
スクリーンショット 2019-07-28 15.45.00.png

処理の流れ

1.モデル(model)クラスにバリデーション(Validation)を設定

validatesメソッドについて

validates(検証するフィールド名, :length => 検証パラメータ)

以下のモデル(model)ではvalidatesメソッドの第一引数にgroupTBLのnameカラムを設定。
第二引数以降に検証パラメータを設定している。この場合、

1.上記スナップショットのnameフォームが空欄であることを非許容
2.nameフォームは本アプリケーションないで一意になること

となる

group.rb
class Group < ApplicationRecord
    has_many :members
    has_many :users, through: :members
    has_many :messages
    validates :name, presence: true, uniqueness: true
end

2.コントローラ(controller)に条件分岐を設定する

saveに成功したらrootに設定したビュー(view)にリダイレクト
savaに失敗したらrenderでedit.html.hamlに再度遷移させる

groups_controller.rb
#必要な箇所だけ抜粋

class GroupsController < ApplicationController

    def create
      @group = Group.new(group_params)
      if @group.save
        redirect_to root_path, notice: 'グループを作成しました'
      else
        render :new
      end
    end


3.ビュー(view)にエラー出力を定義

:point_up:hamlで書いています。

ビューのエラー文を表示したい箇所に「if 〇〇.errors.any?」を記載する。

モデル(model)でバリデーションエラーが発生した場合、modelのerrorsにエラーメッセージが設定さる。さらに、full_messagesでバリデーションのエラーメッセージを配列で取得できる。

@group.errors.full_messagesでエラーを全て表示。
@group.errors.full_messages.countで件数を表示できる。

edit.html.haml
#必要な箇所だけ抜粋
.chat-group-form
  %h1 新規チャットグループ

  = form_for @group do |f|
    - if @group.errors.any?
      .chat-group-form__errors
        %h2= "#{@group.errors.full_messages.count}件のエラーが発生しました。"
        %ul
          - @group.errors.full_messages.each do |message|
            %li= message
    .chat-group-form__field
      .chat-group-form__field--left
        = f.label :name, class: 'chat-group-form__label'
      .chat-group-form__field--right
        = f.text_field :name, class: 'chat__group_name chat-group-form__input', placeholder: 'グループ名を入力してください'

以上ざくっと。

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

deviseについて

Deviseのドキュメントを一通り読んだので、今の自分に必要な部分のみ訳していきました。

1.deviseの導入

Gemfile.rbに

Gemfile.rb
gem 'devise'

この記述をして、

$ bundle install
$ rails g devise:install

これだけで、deviseをインストールができます。

2.deviseのセットアップ

rails g devise:install後、以下のような文章が現れます。

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

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

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

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

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

       root to: "home#index"

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

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

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

       rails g devise:views

1.はメール認証機能を実装したときに、メール認証のメールに記載するURLを設定する部分なので、実際にサービスを運用するときには編集します。deviseのチュートリアルなどを勉強している段階ではあまり関係ないです。

2.はrootのurlを何かしら設定しなさいという内容なので、適当にhomeコントローラーを作って、indexアクションに飛ばしておきます。

$ rails g controller home index
config/routes.rb
#rootにhome#indexを追加
root to: "home#index"

3.flashメッセージを設定しなさいということなのですが、なくてもこの先に進めます。railsでwebアプリを作っていると、application.html.erbの本文は<% yield %>だけなので、layoutディレクトリにflashメッセージを記述するためのパーシャル(_flash.html.erb)を作って、その中にflashメッセージを表示する記述をしておくといいでしょう。

_flash.html.erb
<% flash.each do |key, value| %>
  <%= content_tag(:div, value, class: "flash flash_#{key}") %>
<% end %>

flashメッセージをapplication.html.erbで描画するために、renderを使って結果を表示します。

application.html.erb
<body>
  <%= render 'layouts/flash' %>
  <%= yield %>
</body>

こんなもんでしょうか。

4.はview画面を作り込みたいときに使用します。

$ rails g devise:views

これで下準備ができました。ユーザー登録をするために、Userモデルを作成し、データベースを作りましょう。

3.モデルの作成

$ rails g devise User

1.の言う通りメール認証を追加するならば、ここでConfirmableをアンコメントアウトしておきます。

○○○○○○○○○○_devise_create_users.rb
       Confirmable
       t.string   :confirmation_token
       t.datetime :confirmed_at
       t.datetime :confirmation_sent_at
      <img width="982" alt="スクリーンショット 2019-07-28 13.23.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460758/9a233b84-0dff-a80c-384d-01b96e2c6675.png">
 t.string   :unconfirmed_email # Only if using reconfirmable

機能を解説しているだけなので、ここは何も追加しません。

$ bundle exec rake db:migrate

続いてユーザーモデルを編集しましょう。

user.rb
devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :confirmable
#デフォルトではvalidateableまでの5つの記述がありますが、メール認証の機能を追加するために、:confirmableを追加しておきましょう。

続いて、index.html.erbに簡単なユーザー登録とログアウトボタンを作ります。

index.html.erb
<%= link_to "ユーザー登録", new_user_registration_path %>
<%= link_to "ログアウト", destroy_user_session_path, method: "delete" %>

これで最低限の機能が実装されましたので、実際にユーザー登録機能を使ってみましょう。

$ rails s

devise関連の設定を行ったら、rails sを再度立ち上げること!!!これをしないと、いつまでたっても変更が反映されせん!!!

localhost:3000に接続すると下のような画面が出てきます。
スクリーンショット 2019-07-28 13.16.37.png

ユーザー登録ボタンを押すと、登録画面が現れますので
スクリーンショット 2019-07-28 13.18.49.png

登録後の画面に先ほどの3.で設定した、flashメッセージが出てきます。
スクリーンショット 2019-07-28 13.19.57.png

この時点ではまだログインされておらず、登録しようとしたemailアドレスに認証メールを送った段階のため、コンソールを一度確認します。
スクリーンショット 2019-07-28 13.23.24.png

吐き出されたメッセージをみてみると、登録したアドレスに認証アドレスを記載したメールを送りましたよとあるので(実装してないので、実際はメールはきません。)、aタグのリンクをコピぺして接続すると

スクリーンショット 2019-07-28 13.26.56.png

無事、認証されてログインされました。deviseを導入した時点で、以下のヘルパーメソッドが使えるようになっているので、必要に応じてbefore_actionに付け加えたり、使用しましょう。

before_action :authenticate_user!
#オプションでonlyを設定して、ログインしているユーザーにのみ、設定したアクションへのアクセス権限を与える。デフォルトではすべてのアクションにアクセス制限がかかっています。

user_signed_in?
#ユーザーがログイン済みかどうか判定するメソッド。

current_user
#現在ログインしているユーザーの情報を取得する。

user_session
#sessionの情報を取得する。

#userには各自で設定したモデル名が入るので、memberモデルでdeviseを設定したら、member_signed_in?になります。

4.ストロングパラメーターの設定

デフォルトではユーザー情報はemailとpasswordしか(他にもありますが、ユーザーが設定する情報ではないので割愛)ないので、ユーザー名などを追加で設定したいと考える方は大勢いるでしょう。

しかし、モデルに渡すパラメーターは下記のようにdevise側で設定されている状態のため、追加したい場合はdeviseのコントローラーファイルは作成されないため、applicationコントローラー中で、configure_permitted_parametersメソッドを設定して、許可するパラメーターを追加します。

#デフォルトでdeviseが設定している、モデルに渡せるパラメーター
POST /users/sign_in => devise/session#create #ユーザーのログイン時
=> emailのみ

POST /users/sign_up => devise/resistrations#create #ユーザー登録時
=> email, password, password_confirmationのみ

PATCH /users => devise/resistrations#update #ユーザー情報の更新時
=> email, password, password_confirmationのみ
application_controller.rb
#パラメーターを追加したい場合
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?
  #deviseに関連するアクションか判定して、trueならばparameterを追加する

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys:[:name])
  end
  #sign_upのルーティングに、:nameのパラメーターを許可する。
end

駆け足で解説しましたが、間違っていたらご指摘ください。

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

【Ruby】eachとmapの違い

Rubyで再帰的な処理をしたいときによく使われるeachとmapですが、初心者の方はつまづきやすいので、これらの使い分け方について簡単にまとめます。

each

eachは繰り返し処理をしたい時に使用します。
下の例では配列arrayの各要素の数字を2倍した結果を出力しています。

array = [1, 2, 3]
array.each do |item|
  p item * 2
end
2
4
6
=> [1, 2, 3]

mapとの違いを理解するうえで、戻り値がもとの配列[1, 2, 3]のままである点がポイントとなりますので覚えておいてください。

map

mapは繰り返し処理をした結果を配列として保持するときに使います。
下の例では配列arrayの各要素の数字を2倍した結果を配列resultに詰めています。

array = [1, 2, 3]
result = array.map do |item|
  item * 2
end
=>[2, 4, 6]

eachと違ってブロックの戻り値の結果が配列として返ってきていることがわかります。
もし同じ処理をeachで実装したい場合は下のようになりますが、mapで書くよりも記述量が増えてしまいますね。

array = [1, 2, 3]
result = []
array.each do |item|
  result << item * 2
end
=> [1, 2, 3]

まとめ

eachとmapの使い分けは、処理結果を使うか使わないかによって変わってきます。
処理結果を使いたい場合はeachではなくmapを積極的に使っていけると良いですね!

# 良い例
array.map do |item|
  item * 2
end

# 悪い例
result = []
array.each do |item|
  result << item * 2
end
  • eachは繰り返し処理に使う
  • mapは繰り返し処理の結果を配列にしたいときに使う
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

N+1問題 メモ

  • N+1問題とは
    • SQLクエリが「データ量N + 1回」走ってしまい、取得するデータが多くなるにつれて(Nの回数が増えるにつれて)パフォーマンスを低下させてしまう問題。
    • 関連するテーブルを取得する際に起きる。
clients = Client.limit(10)

clients.each do |client|
  puts client.address.postcode
end 

上記では最初にクライアントを10人検索するのにクエリを1回発行し、次にそこから住所を取り出すのにクエリを10回発行するので、合計で 11 回のクエリが発行されている。
- 解決方法
- includeメソッドを利用
- includesを指定すると、Active Recordは指定されたすべての関連付けが最小限のクエリ回数で読み込まれるようにしてくれる。

clients = Client.includes(:address).limit(10)

clients.each do |client|
  puts client.address.postcode
end

最初の例では 11 回もクエリが実行されましたが、今度の例ではわずか 2 回にまで減る。

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

Railsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。

概要

TECH::EXPERTのカリキュラムでオリジナルのミニアプリを作成する機会があり、
同期で学習中の方がamChartsを使っていてかっこいいなぁ〜と思い、自分なりに調べてみました。
いくつか工夫したところがあったので共有します。
体重管理アプリのようなものを想定してます。
体重を計る日とそうでない日がマチマチにある場合のグラフを作成します。
例:
7月1日 70kg
7月2日 71kg
7月4日 73kg
7月7日 68kg
7月8日 69kg
7月9日 67kg
=>7月3日と7月6日の記録がない

作成する前提

amChartsがCDNで読み込めている 
参考文献amcharts 4 Demos を使ってグラフを作成(amChartsの導入方法が記載されています)
使用するグラフの種類:Line Chart with Scroll and Zoom
Image from Gyazo
diaryモデルにweightカラムがある
diaries_controler.rbにindexアクションが用意されてある

編集するファイル

・コントローラーファイル
・ビューファイル

コントローラーファイル

diaries_controller.rb
class DiariesController < ApplicationController
  def index
    @diaries=Diary.all.order('created_at ASC')
    @weights=@diaries.map(&:weight)
    @dates=@diaries.map{|diary| diary.created_at.strftime('%Y/%m/%d') }
  end
end

今回は、weigthカラムの値ビューファイルに渡します。
配列で渡したいので上記のようにmapメソッドを使いました。
eachでは下記のように記載します。

diaries_controller.rb
##eachの場合
@weights=[]
@diaries.each do |diary|
 @weigth << diary.weight
end

"&:"の記法は処理が1つの場合しか使えないので、@datesはデフォルトの記法です。

ビューファイル

index.erb
<!-- Styles -->
<style>
#chartdiv {
  width: 100%;
  height: 500px;
}
</style>

<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/kelly.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>

<!-- Chart code -->
<script>
am4core.ready(function() {

// Themes begin
am4core.useTheme(am4themes_kelly);
am4core.useTheme(am4themes_animated);
// Themes end

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);

//JSON形式で値を渡す
const weights = <%== JSON.dump(@weights) %>;
const dates = <%== JSON.dump(@dates) %>;
//表示期間を計算
var firstDate = new Date(dates[0])
var lastDate = new Date(dates.slice(-1)[0])
var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1

// Add data
chart.data = generateChartData();

// Create axes
var dateAxis = chart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.minGridDistance = 50;

var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());

// Create series
var series = chart.series.push(new am4charts.LineSeries());
series.dataFields.valueY = "weight";
series.dataFields.dateX = "date";
series.strokeWidth = 2;
series.minBulletDistance = 10;
series.tooltipText = "{valueY}";
series.tooltip.pointerOrientation = "vertical";
series.tooltip.background.cornerRadius = 20;
series.tooltip.background.fillOpacity = 0.5;
series.tooltip.label.padding(12,12,12,12)

// Add scrollbar
chart.scrollbarX = new am4charts.XYChartScrollbar();
chart.scrollbarX.series.push(series);

// Add cursor
chart.cursor = new am4charts.XYCursor();
chart.cursor.xAxis = dateAxis;
chart.cursor.snapToSeries = series;

//不連続な間隔(日付)で投稿された値を表示する
function generateChartData() {
  var chartData = [];
  for (var j =0; j< weights.length; j++){
    for (var i = 0; i < termDate; i++) {
      var newDate = new Date(firstDate)
      newDate.setDate(newDate.getDate() + i); //初日からi日分たす
      if ((new Date(dates[j])) - (newDate)==0){
        weight =weights[j]
        chartData.push({
            date: newDate,
            weight: weight
        });
      }
    }
  }
  return chartData;
}

}); // end am4core.ready()
</script>
<div id="chartdiv"></div>

基本はDemo sourceをコピペしてください。
Image from Gyazo
styleがビューファイルにありますが、本記事の趣旨とズレるのでそのままビューファイルに記載してます。
また、Demo sourceの"visits"は"weight"としてます。(単数形です..Demo sourceはなぜ複数形にしてるんだろう...?)
下記コピペ部分とは違うところの説明です。

1.コントローラーの変数をJavascriptで使えるようにする

index.erb
<script>
//JSON形式で値を渡す
const weights = <%== JSON.dump(@weights) %>;
const dates = <%== JSON.dump(@dates) %>;
</script>

JSONでコントローラーの値をわたします。
こちらはrailsのcontrollerからjavascriptに対して変数を渡すを参考にさせていただきました。

2.表示する期間を算出する

index.erb
<script>
//表示期間を計算
var firstDate = new Date(dates[0]) //一番最初
var lastDate = new Date(dates.slice(-1)[0]) //一番最後
var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1 //表示する期間
</script>

コントローラー側で配列にしたとき、

diaries_controller.rb
@diaries=Diary.all.order('created_at ASC')

としてるので、昇順(日付が古い順)になってます。なので、容易に一番最初を最後を取得できますね。
termDateは期間を計算しました。日付の場合そのまま引き算をできないので、/ 1000 / 60 / 60 / 24としてます。
+1は、たとえば 3日から5日までの期間を出すために"5-3=2"では1日たりないので+1としてます。

3.不連続な間隔(日付)で投稿された値を表示する
Demo sourceだと下記のように表現されているところです。

Demo.erb
<script>
function generateChartData() {
    var chartData = [];
    var firstDate = new Date();
    firstDate.setDate(firstDate.getDate() - 1000);
    var visits = 1200;
    for (var i = 0; i < 500; i++) {
        // we create date objects here. In your data, you can have date strings
        // and then set format of your dates using chart.dataDateFormat property,
        // however when possible, use date objects, as this will speed up chart rendering.
        var newDate = new Date(firstDate);
        newDate.setDate(newDate.getDate() + i);

        visits += Math.round((Math.random()<0.5?1:-1)*Math.random()*10);

        chartData.push({
            date: newDate,
            visits: visits
        });
    }
    return chartData;
}
</script>

簡易的に説明するならば、初期値1200であとは、ランダムで数値をあれやこれやとかえて、
i=1から500まで表現してます。
これはこれで学習するところがあっておもしろかったです。
本記事ではコントローラーから取得した値を下記のようにしてます。

index.erb
<script>
//不連続な間隔(日付)で投稿された値を表示する
function generateChartData() {
  var chartData = [];
  for (var j =0; j< weights.length; j++){
    for (var i = 0; i < termDate; i++) {
      var newDate = new Date(firstDate)
      newDate.setDate(newDate.getDate() + i); //初日からi日分たす
      if ((new Date(dates[j])) - (newDate)==0){
        weight =weights[j]
        chartData.push({
            date: newDate,
            weight: weight
        });
      }
    }
  }
  return chartData;
}
</script>

[1]変数をinteger型の変数を2つ用意してます(i,j)
[2]jはweights.lengthの数まで繰り返します。つまり体重のレコードの数までですね。
[3]iをjにネストしてます。こちらはtermDateの数だけ繰り返します。つまり、”2.表示する期間を算出する”で計算してあげた期間分繰り返します。
[4]表示するのは、あくまで、体重のレコードが存在する日付のみなので、jの日付(体重記録がある日付)とiの日付(newDate)の差が0のときのみ、日付と体重をハッシュの形にし、chartDataに入れてあげます。

完成!!

Image from Gyazo

gifでは"Jul 11"と"Jul 12"に記録がありません。

参考文献

amcharts 4 Demos を使ってグラフを作成
railsのcontrollerからjavascriptに対して変数を渡す

最後に

この記事を書いた目的

・自分なりに工夫した点をアウトプットして、理解を深める。
・あわよくば有識者にフィードバックをもらいたい。
・私と同じ初学者からも奇譚のない意見をもらいたい。(自分だったらどうこうする的な)

筆者について

TECH::EXPERTにて4月27日より52期夜間・休日コースでruby/railsを学習している未経験エンジニアです。
ご不備等ありましたら、ご指摘ください。ちなみに本記事が初投稿になります。
言わずもがなかもしれませんが、趣味はボディメイク・筋トレでございます。
余談ですが、120kg⇨66kgまで減量して大会出場した経験があり
ダイエットについての質問はなんでも答えられるかと思います:muscle:

ひとこと

最後までご覧いただきまして、ありがとうございました。
もし気に入っていただけたら、イイね・ストック・フォローご自由に!

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

#ruby #rails の #FactoryBot で association を使わずに無理やり BuildStrategy ( create / build / attributes_for ) などを反転して条件分けする例

Example of forcing condition inversion of BuildStrategy (create / build / attributes_for) forcibly without using association in #FactoryBot of #ruby #rails

FactoryBot.define do
  factory :company do
    user do
      build_stragy = self.instance_variable_get(:@build_strategy).class

      if build_stragy == FactoryBot::Strategy::Create
        create :user
      else
        build :user
      end
    end
  end
end

Original by Github issue

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

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

#Ruby / #Rails の #FactoryBot で association 記法を使わずに create と attributes_for の挙動を使い分ける例

Ruby / # Rails #FactoryBot example of using create and attributes_for behavior without using association notation

FactoryBot.define do
  factory :user do
    book do |factory_bot|
      if factory_bot.attribute_lists?
        nil
      else
        create :book
      end
    end
  end
end

Original by Github issue

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

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

【Rails5】外部キーの設定メモ

はじめに

よく忘れてしまうのでメモとして残しときます。

前提条件

以下のテーブルを使います。
reference.png

また「ユーザーテーブル」はすでに作成している前提で書いていきます。

外部キーの設定

  1. フォロワーテーブルを作成。
  2. 作成したマイグレーションファイルに外部キーの設定を追加
  3. データベースを更新

方法

# ターミナル
rails g model follower user:references follower:references
user:references

これでreference型のuserカラムを作成しています。

モデルを作成したら以下のマイグレーションファイルも一緒に作成されます。

db/migrate/20190727232828_create_follows.rb
class CreateFollowers < ActiveRecord::Migration[5.2]
  def change
    create_table :followers do |t|
      t.references :user, foreign_key: true
      t.references :follower, foreign_key: true

      t.timestamps
    end
  end
end
reference型について

reference型にすると下記の設定が自動でされます。
①カラム名がuserではなくuser_idとして追加される
②自動でindexが追加される
※reference型を指定しても外部キー制約は設定されていないので注意してください。

foreign_key: true

外部キー制約が設定されます。
参照するテーブルは指定したモデルのテーブルです。
例えば「t.references :user, foreign_key: true」では
カラム名を指定する箇所で「:user」というモデルが指定されているので、usersテーブルが参照先として設定されます。

疑問点

上記の話だと、followerにもuserテーブルを参照先として設定したい時、:userを指定しないといけなくなる。
そうすると2個目のuser_idが出来上がってしまう。エラー出ると思うけど。

解決方法

マイグレーションファイルに参照先のテーブルを指定しましょう。
上記で挙げたマイグレーションファイルを修正します。

db/migrate/20190727232828_create_followers.rb
class CreateFollowers < ActiveRecord::Migration[5.2]
  def change
    create_table :followers do |t|
      t.references :user, foreign_key: true
      #  { to_table: :users }を追加
      t.references :follower, foreign_key: { to_table: :users }
      t.timestamps
    end
  end
end

{ to_table: :users }で参照するテーブルの指定ができます。
これでデータベースを更新してみましょう。

#  ターミナル
rails db:migrate

これで外部キー制約の設定が完了です。

まとめ

1.フォロワーテーブルを作成。

# ターミナル
rails g model follower user:references follow:references

2.作成したマイグレーションファイルに外部キーの設定を追加

db/migrate/20190727232828_create_followers.rb
class CreateFollowers < ActiveRecord::Migration[5.2]
  def change
    create_table :followers do |t|
      t.references :user, foreign_key: true
      #  { to_table: :users }を追加
      t.references :follower, foreign_key: { to_table: :users }
      t.timestamps
    end
  end
end

3.データベースを更新

#  ターミナル
rails db:migrate
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyの正規表現でバイナリファイルからPNG形式の画像を抽出

バイナリファイルからPNGを抜き出してみたくなった、ただそれだけ。Rubyでサクッとやる。

コードと実行例

extract-png.rb
filename = ARGV[0]
prefix = File.basename(filename, ".*")

PNG_REGEXP = /
    \x89PNG\r\n\x1A\n
    \x00\x00\x00\x0D IHDR .{13} .{4}
    .*?
    \x00\x00\x00\x00 IEND \xAE\x42\x60\x82
/mnx

File.binread(filename).enum_for(:scan, PNG_REGEXP).with_index(1) do |data, i|
    File.binwrite("#{prefix}-%04d.png" % i, data)
end
console
$ ruby extract-png.rb imageres.dll
$ ls *.png
imageres-0001.png
imageres-0002.png
...
imageres-0337.png
imageres-0338.png

説明

PNGの仕様

PNGのマジックナンバー(ファイル種別を示す先頭の固定バイト列)は \x89PNG\r\n\x1A\n の8バイト。

その後はチャンクと呼ばれる <データ長><チャンク名><データ本体><CRC-32> の並びが何個も続く。

  • 最初のチャンクは IHDR でデータは常に13バイトなので、チャンク名までの8バイトが固定値となる。マジックナンバーと合わせるとPNGの先頭16バイトが固定値。
  • 最後のチャンクは IEND でデータは無いので、データ長とCRC(巡回冗長検査)も含む12バイトが固定値となる1。これがPNGの末尾。

PNGを厳密に探すには最低でも、マジックナンバーから始まってチャンクが連続することを確認しなければいけない。今回はそこまでするのが面倒なので、バイナリデータからPNGの先頭と末尾を探し出せばOKとした

正規表現の構築

PNGが複数ある場合もあるので、PNGの先頭~末尾はなるべく短くなるよう抽出しなければならない。言い換えると、PNGの途中に先頭や末尾と同じ文字列が登場してはいけない。しっかり対策するには否定先読みや非包含オペレーターのような機能が必要だが、先頭や末尾の条件を長くしておけば誤検知はほぼ防げる2。そのため今回は最短マッチ .*? だけで済ませた。

正規表現にはいくつかオプションを指定している。

  • m: . を改行コードにもマッチさせる。今回はテキストでなくバイナリなので、改行コードを特別扱いさせてはいけない。
  • n: 正規表現の文字コードをバイナリ(ASCII-8BIT)に指定する。
  • x: 正規表現中の空白を無視する。単に可読性を高めるためで、利用は必須ではない。

ファイル入出力と文字列スキャン

本当はファイルが巨大な場合を考慮して少しずつ入力+スキャンできればよかった(StringScannerのIO版みたいな感じ?)が、方法がわからなかったので IO.binread で丸ごと読み込むことにした。※IOクラスに慣れなくてFileクラスを使った。

正規表現による抽出は String#scan でいいものの、何となく以下のことを考慮して Object#enum_for を組み合わせた。

  • scan(PNG_REGEXP) { ... } だと、連番を振る変数を別に用意しなければいけない。
  • scan(PNG_REGEXP).each.with_index(1) { ... } だと、抽出した全PNGデータが一旦配列で保持されてしまいメモリを消費する。

参考


  1. IENDチャンクのCRCはコマンドでも確かめられる。 $ crc32 <(echo -n IEND) #=> ae426082 

  2. 今回試したファイルには、PNGでない場所にもマジックナンバーの列があり、 IHDR まで含めないと誤検知した。 

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

テスト

テスト

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

Ruby*Seleniumでマウス操作actionにpauseさせる

Seleniumでマウス操作したいけど、マウスの動きが速すぎるのか、期待通りに画面操作ができず。。。
なので、とりあえずマウス操作にpauseを加えようと思ったところ若干はまったのでメモです。

動かないコード

下記のドキュメントを参考に。
https://www.rubydoc.info/gems/selenium-webdriver/Selenium%2FWebDriver%2FW3CActionBuilder:pause

キーボード操作時はkey_inputをpauseに渡せばいいよって書いてあったので、
マウス操作ならpointer_inputでも渡せばいいかと思い下記を実行。

sample.rb
action_builder = driver.action

pointer = action_builder.pointer_input

elm = driver.find_element(:id, 'element_id')
driver.action.click_and_hold(elm) \
  .move_by(50,2).pause(pointer, 0.5) \
  .move_by(50,2).pause(pointer, 0.5) \
  .move_by(50,2).pause(pointer, 0.5) \
  .perform

pointer_input ないよっていわれる。そしてkey_inputもないらしい。ドキュメントに嘘つきやん。

これで動いた

sample.rb
action_builder = driver.action

pointer = action_builder.pointer_inputs[0]

elm = driver.find_element(:id, 'element_id')
driver.action.click_and_hold(elm) \
  .move_by(50,2).pause(pointer, 0.5) \
  .move_by(50,2).pause(pointer, 0.5) \
  .move_by(50,2).pause(pointer, 0.5) \
  .perform

なんでArrayで実装されてるのか謎。とりあえず[0]指定してみたけど期待通りに動いたからまあいっか。

関係ないけど

最初nodejsで実装しようとしてたんですが、そもそもActionsがないと怒られる…。

{ UnknownCommandError: Unrecognized command: actions
    at buildRequest (C:\nodejs_scripts\node_modules\selenium-webdriver\lib\http.js:375:9)
    at Executor.execute (C:\nodejs_scripts\node_modules\selenium-webdriver\lib\http.js:455:19)
    at Driver.execute (C:\nodejs_scripts\node_modules\selenium-webdriver\lib\webdriver.js:696:38)
    at process._tickCallback (internal/process/next_tick.js:68:7) name: 'UnknownCommandError', remoteStacktrace: '' }

http.jsにはActions定義されてるっぽいし謎すぎるから諦めてrubyで実装しました。これの原因わかる方いたら教えてください。。。

そもそもselenium使うならpython使えってことなのかな・・・・:innocent:

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

Rubyのシンタックス勉強用(正規表現 編)(更新中)

Rubyのシンタックスを息を吸うように書くために、少しでも理解が怪しいシンタックスを繰り返したくために書きます。

今回は正規表現です。これこそ、繰り返し書いて、息を吸うようにかけるようになりたい。

また、本記事で扱う正規表現は、初心者向けなため100%完璧に正しい表現でなく、そこそこの精度の表現で妥協しています。そこは、ご承知おきください。そこそこでもないぞ、と言う場合は、ご指摘ください。

正規表現は、まずパターンを見つけて言語化することが大事で、それができればあとは、そのプログラムを記述するだけです。

問題の確認は、以下のサービスを使っています。

Rubular: Rubyで動作する正規表現を試せるオンラインエディタ

ここで学ぶ正規表現のメタ文字

メタ文字 意味
\d 1個の半角数字 0123456789
\d\d\d\d 4個の半角数字 1111
{n,m} 直前の文字やパターンがn回以上、m回以下連続する -
\d{3,5} 3個~5個連続する半角数字 123 や 12345
[AB] AかBのいずれか1文字 -
- []の中で使うと文字の範囲を表す -
[a-z] aかbかcか...かzのいずれか1文字 -
[-az],[az-] aかzかハイフンのいずれか1文字 -
[0-9] 0か1か2か3か...か9のいずれか1文字 -
. 改行以外の任意の1文字
? 直前の文字やパターンが1回、もしくは0回現れる -
.? 改行以外の任意の文字が1回、もしくは0回現れる -
* 直前の文字やパターンが0回以上連続する -
.* 直前の文字の0回以上の繰り返し -
+ 直前の文字が1回以上の繰り返し -
\ メタ文字のエスケープ -
\w 半角英数字とアンダースコア1文字 -

問題1(電話番号)

日本の固定電話,携帯電話に基本マッチする正規表現を書いてください。

電話:03-1234-5678
電話:06-9999-9999
電話:090-1234-5678
電話:0799-12-3450
電話:04982-2-2002
郵便番号:150-0012

パターン1

総務省|電気通信番号制度|電話番号に関するQ&A

を見ると、

半角数字が2個~5個 + ハイフン(-) + 半角数字が1個~4個 + ハイフン(-) + 半角数字が4個

で、基本マッチすることがわかりました。

正解1(電話番号)

\d{2,5}-\d{1,4}-\d{4}

問題2(電話番号)

番号自体は、問題1と同じですが、ハイフンでなく()の場合もありますよね。それにマッチする正規表現を書いてください。

電話:03-1234-5678
電話:06-9999-9999
電話:090-1234-5678
電話:0799-12-3450
電話:04982-2-2002
電話:03-1234-5678
電話:06(9999)9999
電話:090-1234-5678
電話:0799(12)3450
電話:04982-2-2002
郵便番号:150-0012

パターン2

半角数字が2個~5個 + ハイフン(-) か ( + 半角数字が1個~4個 + ハイフン(-)か) + 半角数字が4個

で、基本マッチすることがわかります。

正解2(電話番号)

\d{2,5}[-(]\d{1,4}[-)]\d{4}

問題3(表記ゆれを許容して社名を抽出する)

表記ゆれの多いワード「引越し」をサービス名にした引越し侍には、以下のような表記ゆれがある可能性があります。

引っ越し侍
引越侍
引越し侍
引っ越し 侍
引越侍
引越し 侍
引越し・侍

パターン3

  • 引っ越引越は、ちいさい「つ」()があっても、無くても許容できるようにするために .? を使用する
  • の間は何があってもいいように *? を使用する

で、基本マッチすることがわかります。

メタ文字 意味
.? 任意の1文字か0文字 -
.* 直前の文字の0回以上の繰り返し -

正解3(表記ゆれを許容して社名を抽出する)

.?越.*

ただこれだと、例えば「引っ越さない侍」とかでも許容してしまうので、どこまで厳密に抽出するかはその時の状況次第になります。

問題4(HTMLから文字列を抽出)

よく見る都道府県のHTML。ここから、valueの中身と、タグの中身のテキスト(pref_xxx,北海道)がある行ごと抽出する正規表現を書いてください。

<select name="pref_name">
  <option value="pref_hokkaido">北海道</option>
  <option value="pref_aomori">青森県</option>
  <option value="pref_iwate">岩手県</option>
</select>

パターン4

pref_aomoripref_hokkaidoからパターンを導き出すと、

value= + " + aからzのアルファベットかアンスコが1文字以上 + "

  • アルファベットかアンスコが1文字以上 = [a-z_]
  • 直前のが1文字以上 = +

つまり [a-z_]+

  • 任意の1文字以上 = [a-z_]
  • 直前のが1文字以上 = +

タグの中身のテキストは、何かしらテキストが1文字以上あるので .+

で、基本マッチすることがわかります。

正解4(HTMLから文字列を抽出)

行全体をマッチさせるとしたら

<option value="[a-z_]+">.+<\/option>

optionの閉じタグにあるバックスラッシュは、/のエスケープを目的にしています。
Rubyは/を正規表現オブジェクトとして認識してしまうので。

問題5(HTMLをCSVに変換 - キャプチャ)

問題4にあるvalueの中身と、タグの中身のテキスト(pref_xxx,北海道)をキャプチャする正規表現を書いてください。

Rubyにおけるキャプチャとは何ができるやつなのか

  • かっこ()内の文字列を参照出来るようにすることができる
  • $1 ,$2 ...に保存し、文字列を変数として参照できる(置換にも使える)
<select name="pref_name">
  <option value="pref_hokkaido">北海道</option>
  <option value="pref_aomori">青森県</option>
  <option value="pref_iwate">岩手県</option>
</select>

パターン5

基本はパターン4と同じです。

キャプチャに関してはキャプチャしたい文字列を()で閉じ込めます。

正解5(HTMLをCSVに変換 - キャプチャ)

<option value="([a-z_]+)">(.+)<\/option>

キャプチャされてることがわかります。

Rubular__a_Ruby_regular_expression_editor.png

Rubularは置換できないので、代わりにエディタのAtomにやらせましょう(Atomじゃなくてもできると思います)

  • Atomを正規表現モード(.*ボタン押す)にして
  • 検索窓に<option value="([a-z_]+)">(.+)<\/option>入りつけて
  • 置換後の形式($1,$2)を下の検索窓に借りつけてreplace Allボタンを押す
    • $1$2 はそれぞれキャプチャされた1番目の文字列と2番目の文字列を表しています

c995ca1b8502d2502e43ef422f52894f.gif

問題6(HTMLをCSVに変換 - キャプチャ その2)

基本はパターン5と同じですが。valueに値がなくて、デフォでselectedがついてる場合もよくありますよね?その場合でもキャプチャできる正規表現を書いてください。

<select name="pref_name">
  <option value="" selected>都道府県</option>
  <option value="pref_hokkaido">北海道</option>
  <option value="pref_aomori">青森県</option>
  <option value="pref_iwate">岩手県</option>
</select>

パターン6

value= + " + aからzのアルファベットかアンスコが1文字以上または、何も無い + "

  • アルファベットかアンスコが1文字以上 = [a-z_]
  • 直前のが0回以上 = *

つまり [a-z_]*

selectedに関しては、ある場合と、無い場合を考慮し、かつキャプチャされたく無いので?:をつける。

つまり (:? selected)?

最後の?は、selectedが無い場合も考慮して書いています。

タグの中身のテキストは、何かしらテキストが1文字以上あるので .+

で、基本マッチすることがわかります。

正解6(HTMLをCSVに変換 - キャプチャ その2)

<option value="([a-z_]*)"(?: selected)?>(.+)<\/option>

これで、キャプチャされてることもわかるかと思います。
また、エディタのAtomで置換できることもわかります。

128a66ab83100eeb44c803c46bee5c9c.gif

正解6に関してはリファクタも可能で、

a-z_この部分ですが、\wで代替え可能です。なぜなら、\wは、半角英数字とアンダースコア1文字を表すからです。

<option value="([\w]*)"(?: selected)?>(.+)<\/option>

参考

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