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

merge

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

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

jqueryでHTTP通信をしないページ遷移

タイトル通り

HTTP通信を行わずに、webページを遷移する方法を身につけたので共有します。
htmlはhamlで記入しているのでよろしくお願いします。

結論としてはまだ表示させたくないページには大元のclassにdisplay:noneを当てる

まずは、必要なwebページのhtmlコードを一つのhtmlファイルに全て書き出します。そして、最初に表示されないページの大元のクラスにdisplay:noneが当たるクラスを与えます。下のコードでは.yabaiがそのクラスになります
以下のコードと画像を見てください

haml
.zyuuhati
  .keikoku-box
    %P.top-keikoku
      = fa_icon 'venus-mars'
      %span 警告!
    %p.keikoku-bunsyo 当サイトは18歳以下の利用を固く禁じます。あなたは18歳以上ですか?
    %a.ryoukai はい //このボタンを押すとページが切り替わります
    %a.nigeru いいえ

.waller.yabai //.yabaiにはdisplay:noneがかかっているので最初は見えません
  %P.yabababa まじ卍字
css
.zyuuhati{
 background-color: gray;
 height: 100%
 padding :15% 15%;

}
.keikoku-box{
  background-color: #fba933;
  height: 100%;
  width: 100%;
  padding: 20% 10% 10% 10%;
}

.top-keikoku{
  text-align: center;
  font-size: 30px;
  font-weight: 200;
  color: red;
}

.keikoku-bunsyo{
  margin-top: 70px;
  font-size:25px;
  color: black;
}

.ryoukai{
  display: block;
  text-align: center;
  border-radius: 20%;
  line-height: 40px;
  width: 200px;
  height: 40px;
  background-color: aqua;
  font-size: 20px;
  margin: 20px auto;
  color: white;
}

.nigeru{
  display: block;
  text-align: center;
  border-radius: 20%;
  line-height: 40px;
  width: 200px;
  height: 40px;
  background-color: red;
  font-size: 20px;
  margin: 20px auto;
  color: white;
}
.yabai{
  display: none;
}

.waller{
  background-color: #ef5185;
  height: 700px;

}

.yabababa{
  color: white;
  font-size: 50px;
}

名称未設定.mov.gif


「はい」のリンクをクリックした瞬間、一つ目のページが消え、二つ目のページが現れましたね。
このからくりは、「はい」のクリックを発火点にして、1枚目のページのクラスにyabaiクラスを与え、
2枚目のページからyabaiクラスを取り除くという方法で実装しています。
具体的なコードは以下の通りです。

jquery
$(function(){
  $('.ryoukai').on('click', function(e){
    e.preventDefault();
    $('.zyuuhati').addClass('yabai'); //最初の画面をdisplay:noneにします。
    $('.waller').removeClass('yabai'); //次のページを表示させます
  });
});



この方法で一つのURL上でページ遷移することができます!

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

プロキシに阻まれたVagrantからRDSに接続する

プロキシに阻まれて何もできない・・・

  • ホストマシン(以下「ホスト」)は、かなり堅い認証プロキシ(以下「鉄壁プロキシ」)経由でインターネットに接続されている(Vagrantの通信はことごとく407になる)
  • 開発用に、ホスト上のVagrant環境からの通信だけを捌くプロキシサーバ(以下「開発プロキシ」)をEC2に構築する(許可は取ってる)
  • EC2にsquidを入れ、Vagrantからの通信を受ける
  • 別で動いているWEBアプリケーションのRDSに対して接続できるようにし、データを取得できるようにする

環境

  • Windows 10 1803(ホスト)
  • Vagrant 2.2.2
  • VirtualBox 6.0.0
  • CentOS 7.6.1810(ゲスト)
  • Ruby 2.6.0
  • Rails 5.2.2
  • vagrant-vbguest 0.18.0
  • vagrant-proxyconf 2.0.4
  • dotenv 2.7.4
  • mysql 5.7(RDS)

例として設定する項目と値

記事内での設定例については、ここに記す値を利用する

項目 設定値
ホストのグローバルIPアドレス 111.222.333.444
squid.conf内でのルール名 myrule1
開発プロキシのユーザ名 ec2-user
開発プロキシのsquid用ポート 60008
開発プロキシのElastic IP 55.555.5.555
開発プロキシのプライベートIP 172.16.11.11
開発プロキシのpemファイル nice_stick.pem
RDSのエンドポイント xxxxxxxx.yyyyyyyy.ap-northeast-1.rds.amazonaws.com
RDSのポート 3306
RDSのパブリックアクセシビリティ 有効
プロジェクト名 nice_stick
.env DB_USERNAME nice_stick
.env DB_PASSWORD melonpan
  • RDSは、セキュリティグループで55.555.5.555からの3306番の接続を許可し、mysql側で、nice_stick@55.555.5.555 からのパスワード利用での接続を許可しておく

melonpanプロジェクトの本番DBに、nice_stickプロジェクトの開発環境からアクセスできるようにしていく

EC2(開発プロキシ側)の設定

  • Amazon Linux 2でインスタンス作成、ElasticIPも割り当てておく
  • セキュリティグループのインバウンドに、ホストのグローバルIPアドレスを設定する
  • したがって、鉄壁プロキシ経由でホストのグローバルIPアドレスに対して22番ポートでの接続ができるようにしておく

今回は、下記の設定を行う

terminal(開発プロキシ側)
sudo yum -y squid install
sudo vi /etc/squid/squid.conf
squid.conf(抜粋)
(省略)
acl localnet src fe80::/10 ← の下に
acl myrule1 src 111.222.333.444/32  を追加

(中略)
http_access allow localhost ← の下に
http_access allow myrule1 を追加

(中略)
# Squid normally listens to port 3128 ← の下に
http_port 60008  を追加

上記の編集を行って保存、

terminal(開発プロキシ側)
sudo squid -k parse
sudo systemctl restart squid.service
sudo systemctl enable squid.service

を行う。squid -k parse は、squid.confの構成のチェックをしているので、エラーがあれば修正する。
エラーがある状態でsystemctl restartしても、エラーが発生するので、きちんと直す。

省略するが、ダイジェスト認証などの認証機能を導入しておくほうが(かなり)望ましい。
https://skkskynw.hateblo.jp/entry/2016/09/04/165725

Vagrant側の設定

ホストへのVagrant/VirtualBoxのインストールは行われているものとする

Vagrantプラグインのインストール

プロジェクトルートで、下記コマンドを実行する

cmd.exe
vagrant plugin install vagrant-vbguest
vagrant plugin install vagrant-proxyconf
vagrant plugin install dotenv

インストール後、

cmd.exe
vagrant plugin list

を行い、上記3つのプラグインがインストールされていることを確認する

.envとVagrantfileの編集

.env
PROXY_URL=http://55.555.5.555:60008
Vagrantfile(抜粋)
Dotenv.load

Vagrant.configure("2") do |config|
  (中略)
  if Vagrant.has_plugin?("vagrant-proxyconf")
    config.proxy.enabled
    config.proxy.http     = ENV['PROXY_URL']
    config.proxy.https    = ENV['PROXY_URL']
    config.proxy.no_proxy = "localhost,127.0.0.1"
  end
  (中略)
  config.vm.synced_folder ".", "/vagrant", type: "virtualbox"
end

上記設定を行い、vagrant up する

Ruby、Rails、mysqlクライアントのインストール等はすませておく。
Railsプロジェクトも作成しておく(database.ymlを編集するので)

SSHログインと開発プロキシの動作確認

vagrantの通信がプロキシサーバから出ているかを確認する

terminal(開発プロキシ側)
[ec2-user@172.16.11.11 ] $ sudo tail -f /var/log/squid/access.log
cmd.exe(vagrant側,sshログイン後)
[vagrant@localhost] curl ifconfig.io

ec2側ターミナルにアクセスログが記録され、vagrant側ターミナルに開発プロキシのElastic IPが返ってくればOK。
これで開発自体はできる。

RDSに接続するために

ここからが本題。
上記の設定値のとおり、今回接続するRDSは本番用であり、接続はRDSのセキュリティグループ、mysqlのユーザ管理で制御されている。

AWSとVagrant環境のイメージは下記のとおり。
proxying_graph.JPG

図は https://www.draw.io/ を利用して作成

この状態で、database.ymlに下記のような設定をしても、接続できずにしばらくしたあと110エラーが返ってくる

database.yml
melonpan_production_for_nice_stick_development:
  <<:default
  host: xxxxxxxx.yyyyyyyy.ap-northeast-1.rds.amazonaws.com
  database: melonpan_production
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>

鉄壁プロキシを使わないネットワークで、同じ構成の環境を作成して実験したところ、上記の記述でも接続できた。
また、開発プロキシにSSH接続してRDSへ接続しても、正常に接続できた。
したがって、vagrant内のプロジェクトでも、この状態では、RDSへの接続で利用される外部ネットワーク接続はVagrantを通じた「開発プロキシ」ではなく、ホストに通じている「鉄壁プロキシ」だということがわかる。これはmysqlコマンドでも同様だった。
mysqlの接続は、AWS内にすら届いておらず、鉄壁プロキシの段階ではじかれていたということ。
(これ自体を回避できる方法があったらコメントください)

てっきり、ifconfigコマンドで開発プロキシのElasticIPが返ってきていたので、開発プロキシ経由で接続してくれると思っていた。

SSHポートフォワーディングの利用による解決

上記の図の通り、開発プロキシはRDSと同じサブネットにいるので、Vagrantから開発プロキシに対してSSHポートフォワードしてあげれば繋がるのではと考えた。

https://qiita.com/nishidataishi/items/40928debaddeb2e33d1d や、
https://cloudpack.media/9675 を参考に、

プロジェクトルートに pemfiles ディレクトリを作成。

Vagrantfile.rbのsynced_folder部分を下記に書き換えて、開発プロキシのpemファイルをpemfilesディレクトリ配下にコピー。
(※ sshコマンドがpemファイルの権限でエラーを吐くので、600にする必要がある。ゲスト側でchmodしても変えられない。)

この段階で、/pemfiles ディレクトリをgitignoreにしておく(超重要)

.gitignore
/pemfiles  ← を追加
Vagrantfile
config.vm.synced_folder ".", "/vagrant", type: "virtualbox", :mount_options => ['dmode=775', 'fmode=775']
config.vm.synced_folder "./pemfiles", "/vagrant/pemfiles", type: "virtualbox", :mount_options => ['dmode=600', 'fmode=600']
cmd.exe(vagrant側,sshログイン後)
[vagrant@localhost] sudo ssh -f -N -L 60008:xxxxxxxx.yyyyyyyy.ap-northeash-1.rds.amazonaws.com:3306 -i /vagrant/pemfiles/nice_stick.pem ec2-user@55.555.5.555

を実行して、バックグラウンドでポートフォワーディングさせるように設定。

database.ymlを下記のように書き換え

database.yml
melonpan_production_for_nice_stick_development:
  <<:default
  host: 127.0.0.1
  port: 60008
  database: melonpan_production
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>

これでRailsサーバを起動したところ、RDSに接続してデータを取得できた。:tada:
都度設定するのは面倒なので、後で.bashrcあたりにでも書き込んでおく。

本番サーバでは不要な手続きなので、developmentとproduction/stagingでは、database.yml での記述を変える。

database.yml(本番記述例)
melonpan_production_for_nice_stick_production:
  <<:default
  host: xxxxxxxx.yyyyyyyy.ap-northeash-1.rds.amazonaws.com
  database: melonpan_production
  username: <%= Rails.application.credentials.melonpan_production_database_user %>
  password: <%= Rails.application.credentials.melonpan_production_database_pass %>

開発時のmelonpan_productionへの接続は、melonpan_production_for_nice_stick_developmentを、
本番はmelonpan_production_for_nice_stick_productionを使うようにする
(modelディレクトリ内で、melonpanモジュールを作っているのは、本体と別アプリのテーブルの名前空間を分けて名前衝突を防ぐため)

app/model/melonpan/melonpan_db_connection.rb
#frozen_string_literal: true

module Melonpan
  class MelonpanDbConnection < ActiveRecord::Base
    self.abstract_class = true
    establish_connection: "melonpan_production_for_nice_stick_#{Rails.env}".to_sym
  end
end

あとは、実際に接続してデータを取得するクラスで、MelonpanDbConnectionを継承すればOK。
これで、開発環境はポートフォワーディング経由で、本番環境は直接接続というように切り分けられた。

P.S.

この問題の解決に実働30時間も費やしたのがくやしいし認証プロキシつらい。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Sprockets::FileNotFoundを解決する

エラー出現動作

gemを追加後localhost:3000を更新

エラー内容

Sprockets::FileNotFound

原因

gemを追加する以前にRailsサーバーを立ち上げている。

解決

Ctrl + CでRailsサーバーを切り、rails sで再起動する。

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

Sprockets::FileNotFoundを解決する。

エラー出現動作

gemを追加後localhost:3000を更新

エラー内容

Sprockets::FileNotFound

原因

gemを追加する以前にRailsサーバーを立ち上げている。

解決

Ctrl + CでRailsサーバーを切り、rails sで再起動する。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Cannot find module '@rails/webpacker' を解決する

対象者

Rails5 + webpack のプロジェクトを作成したが、以下エラーが出現する方

エラー

$ bin/webpack-dev-server
The CLI moved into a separate package: webpack-cli.
Please install 'webpack-cli' in addition to webpack itself to use the CLI.
-> When using npm: npm install webpack-cli -D
-> When using yarn: yarn add webpack-cli -D
module.js:557
    throw err;
    ^

Error: Cannot find module '@rails/webpacker'
    at Function.Module._resolveFilename (module.js:555:15)
    at Function.Module._load (module.js:482:25)
    at Module.require (module.js:604:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (.../appname/node_modules/webpack-dev-server/bin/webpack-dev-server.js:64:1)
    at Module._compile (module.js:660:30)
    at Object.Module._extensions..js (module.js:671:10)
    at Module.load (module.js:573:32)
    at tryModuleLoad (module.js:513:12)
    at Function.Module._load (module.js:505:3)

原因

@rails/webpackerがインストールされていない。

(私の場合、yarnがインストールいなかった為、rails new app --webpack=vue実行後にrails/webpackerがインストール出来ない旨のエラーが出力されていた。)

解決

yarnをインストールし、Railsプロジェクトを再作成する。

手順

1. yarnをインストール

# インストール
$ brew install yarn

# インストール出来たか確認
$ yarn -v
1.17.3

2. Rails + webpackプロジェクトを新規作成

$ rails new app --webpack=vue
$ cd app
$ rails g scaffold pages
$ rails db:migrate

3. ファイルを編集

2.のコマンド実行により作成された以下2つのファイル
index.html.erb, routes.rb
を下記のように置き換える。

app/views/pages/index.html.erb
<%= javascript_pack_tag "hello_vue" %>

config/routes.rb
Rails.application.routes.draw do
  root "pages#index"
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

4. Railsサーバーを起動

$ rails server

5. ローカルホストを確認

http://localhost:3000/ にアクセス

以下のように表示されたら成功。
スクリーンショット 2019-07-28 18.14.34.png

以上。

参考

https://github.com/rails/webpacker/issues/1295

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

【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で続きを読む

日付をX軸としたamChartsの折れ線グラフで初期表示(ズーム後)を直近の1週間表示に変更する

概要

amChartsDate Based Dataを使用しグラフを作成します。
こちらのグラフは起動時に全体を表示し、”最終日からある割合”の日付をズームして表示します。(かっこいい)
Demo source
Image from Gyazo
この”最終日からある割合””直近の1週間”に変更していきたいとおもいます。(上のDemo sourceのgifでは直近の8日にプロットされた値が表示されてますね)

作成する前提

amChartsがCDNで読み込めている 
参考文献amcharts 4 Demos を使ってグラフを作成(amChartsの導入方法が記載されています)
使用するグラフの種類:Date Based Data
使用するモデル:Railsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。において作成した体重管理アプリをモデルに作成します。コントローラーファイルなどの記述はこちらを参考にしてください。

編集するファイル

・ビューファイル

ビューファイル(Demo source)

まずは、Demo sourceがどういう構造になっているか見てみましょう。

demo.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/animated.js"></script>

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

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

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

// Add data
chart.data = [{
  "date": "2012-07-27",
  "value": 13
}, {
  "date": "2012-07-28",
  "value": 11
}, {
  "date": "2012-07-29",
  "value": 15
}, {
//~省略~
  "date": "2013-01-30",
  "value": 81
}];

// Set input format for the dates
chart.dateFormatter.inputDateFormat = "yyyy-MM-dd";

// Create axes
var dateAxis = chart.xAxes.push(new am4charts.DateAxis());
var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());

// Create series
var series = chart.series.push(new am4charts.LineSeries());
series.dataFields.valueY = "value";
series.dataFields.dateX = "date";
series.tooltipText = "{value}"
series.strokeWidth = 2;
series.minBulletDistance = 15;

// Drop-shaped tooltips
series.tooltip.background.cornerRadius = 20;
series.tooltip.background.strokeOpacity = 0;
series.tooltip.pointerOrientation = "vertical";
series.tooltip.label.minWidth = 40;
series.tooltip.label.minHeight = 40;
series.tooltip.label.textAlign = "middle";
series.tooltip.label.textValign = "middle";

// Make bullets grow on hover
var bullet = series.bullets.push(new am4charts.CircleBullet());
bullet.circle.strokeWidth = 2;
bullet.circle.radius = 4;
bullet.circle.fill = am4core.color("#fff");

var bullethover = bullet.states.create("hover");
bullethover.properties.scale = 1.3;

// Make a panning cursor
chart.cursor = new am4charts.XYCursor();
chart.cursor.behavior = "panXY";
chart.cursor.xAxis = dateAxis;
chart.cursor.snapToSeries = series;

// Create vertical scrollbar and place it before the value axis
chart.scrollbarY = new am4core.Scrollbar();
chart.scrollbarY.parent = chart.leftAxesContainer;
chart.scrollbarY.toBack();

// Create a horizontal scrollbar with previe and place it underneath the date axis
chart.scrollbarX = new am4charts.XYChartScrollbar();
chart.scrollbarX.series.push(series);
chart.scrollbarX.parent = chart.bottomAxesContainer;

chart.events.on("ready", function () {
  dateAxis.zoom({start:0.79, end:1});
});

}); // end am4core.ready()
</script>

<!-- HTML -->
<div id="chartdiv"></div>

なんだか難しそうで、なにを書いているかパッとみたただけではわかりませんね。
ただ、データが記載されているところはわかるのではないでしょうか?

demo.erb
<script>
// Add data
chart.data = [{
  "date": "2012-07-27",
  "value": 13
}, {
  "date": "2012-07-28",
  "value": 11
}, {
  "date": "2012-07-29",
  "value": 15
}, {
//~省略~
  "date": "2013-01-30",
  "value": 81
}];
</script>

↑ここです。
この部分を体重管理アプリのモデルに合わせていきます。

ビューファイル(本アプリ)

まずは、コントローラーから変数をもってきて算出までします。

index.erb
<script>
//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 //表示する期間
</script>

細かいところはRailsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。を参照してください。(本記事をそのままコピペしても動きません)
さて、Demo sourceのchart.dataの値を下記のように変えてください。

index.erb
<script>
// Add data
  chart.data = [];
  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]
        chart.data.push({
            date: newDate,
            weight: weight
        });
      }
    }
  }
</script>

valueはRailsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。のときと同様にweightに変更してます。weightの変更箇所はその他にもあるのですべて置換してあげてください。

初期表示(ズーム後)を直近の1週間表示にする

本記事の本題です。
まずはDemo sourceからズームっぽい記述を探してみましょう。後方から9行目付近をみてください。

demo.erb
<script>
chart.events.on("ready", function () {
  dateAxis.zoom({start:0.79, end:1});
});
</script>

{start:0.79, end:1}の数値を試しに{start:0, end:1}かえてみてください。
変えるときは、【小ネタ】amChartsのDemo sourceをVScodeなどのエディタにコピペせず、ブラウザ上でプロパティを変更し表示を確認する方法を使うと便利です。
Image from Gyazo
zoomされず、全体が表示されました。
ということは、startの数値を変更すれば直近の1週間が表示できそうです。

全体の日数はtermDateという変数で取得できてるので、
1週間は全体の日数のどれくらいの割合か出してみます。
例:termDateが70日の場合
式: 7/termDate=0.1
0.1という数値をstartに入れては駄目です。なぜならstartは0に近いほど、小さい値(日付)を取得します。
なので0.1を入れると0.1≦x≦1の範囲になり(endが1の場合)、一番古い日付から1週間後から最新の日付を範囲としてしまいます。
では、どうするかというと1から引いてあげれば良いです。

{start:1-7/termDate, end:1}としてあげます。

こうすることで0.9≦x≦1となり、直近の1週間の割合がだせます。
最後にビューファイルの全体を記載します。

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);
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 = [];
  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]
        chart.data.push({
            date: newDate,
            weight: weight
        });
      }
    }
  }

// Set input format for the dates
chart.dateFormatter.inputDateFormat = "yyyy-MM-dd";

// Create axes
var dateAxis = chart.xAxes.push(new am4charts.DateAxis());
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.tooltipText = "{weight}"
series.strokeWidth = 2;
series.minBulletDistance = 15;

// Drop-shaped tooltips
series.tooltip.background.cornerRadius = 20;
series.tooltip.background.strokeOpacity = 0;
series.tooltip.pointerOrientation = "vertical";
series.tooltip.label.minWidth = 40;
series.tooltip.label.minHeight = 40;
series.tooltip.label.textAlign = "middle";
series.tooltip.label.textValign = "middle";

// Make bullets grow on hover
var bullet = series.bullets.push(new am4charts.CircleBullet());
bullet.circle.strokeWidth = 2;
bullet.circle.radius = 4;
bullet.circle.fill = am4core.color("#fff");

var bullethover = bullet.states.create("hover");
bullethover.properties.scale = 1.3;

// Make a panning cursor
chart.cursor = new am4charts.XYCursor();
chart.cursor.behavior = "panXY";
chart.cursor.xAxis = dateAxis;
chart.cursor.snapToSeries = series;

// Create vertical scrollbar and place it before the value axis
chart.scrollbarY = new am4core.Scrollbar();
chart.scrollbarY.parent = chart.leftAxesContainer;
chart.scrollbarY.toBack();

// Create a horizontal scrollbar with previe and place it underneath the date axis
chart.scrollbarX = new am4charts.XYChartScrollbar();
chart.scrollbarX.series.push(series);
chart.scrollbarX.parent = chart.bottomAxesContainer;

chart.events.on("ready", function () {
  dateAxis.zoom({start:1-7/termDate, end:1});
});

}); // end am4core.ready()
</script>

<!-- HTML -->
<div id="chartdiv"></div>

完成!!

Image from Gyazo
Jul22~Jul28の直近の1週間が表示されてますね。

参考文献

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

最後に

この記事を書いた目的

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

筆者について

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

ひとこと

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

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

Rails + Cropper.jsで画像トリミング機能を実装する

概要

Cropper.jsを用いてRailsでトリミング機能付き画像アップロード機能を実装してみます。
cropper_demo.gif

ソースコードは以下に置いてます。
https://github.com/Tak-Iwamoto/jquery-cropper-rails

環境

Rails 5.2.2
Bootstrap 4.3.1
active storage
mini_magick 4.9.4

active storage

active_storageを利用するために以下のコマンドを実行してください。
rails active_storage:install
rails db:migrate

Cropper.jsの読み込み

https://github.com/fengyuanchen/cropperjs
上のgithubからcropper.min.jsとcropper.min.cssを取得し、Railsの中に配置しましょう。サードパーティライブラリはvendorディレクトリの中に入れることが推奨されており、vendorの中にassets/javascriptsとassets/stylesheetsディレクトリを作成し、それぞれcropper.min.jsとcropper.min.cssを配置します。
https://github.com/fengyuanchen/jquery-cropper
また、JQueryを用いるために上のgithubからjquery-cropper.min.jsを取得し、同様にRailsの中に配置します。最後にapplication.jsとapplication.scssに以下のように編集します。
application.js
//= require rails-ujs
//= require jquery
//= require jquery_ujs
//= require cropper.min.js
//= require jquery-cropper.min.js
//= require activestorage
//= require turbolinks
//= require_tree .
//= require popper
//= require bootstrap-sprockets

application.scss
*= require_tree .
*= require cropper.min.css
*= require_self
*/

これでCropper.jsが使えるようになりました。

modelとcontrollerを作成

デモのためのuserモデル、コントローラーを作成します。

user.rb
class User < ApplicationRecord
  attr_accessor :x, :y, :width, :height
  has_one_attached :image
end
users_controller.rb
class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    @user.save
    session[:crop_x] = user_params[:x]
    session[:crop_y] = user_params[:y]
    session[:crop_width] = user_params[:width]
    session[:crop_height] = user_params[:height]

    redirect_to user_path @user
  end

  def show
    @user = User.find(params[:id])
  end

  private
    def user_params
      params.require(:user).permit(:image, :x, :y, :width, :height)
    end
end

Cropper.jsでトリミングした結果のパラメータをsessionで保持し、のちに画像を表示するときに用いていますが、このやり方は筋が悪い気がしています... active_storageで加工した画像を保存する方法が分からなかったため、この方法で行っていますが、おそらく他にいい方法があると思います。

画像アップロードフォーム

トリミング画像をアップロードするためのフォームを作成します。

new.html.erb
<div class="container">
  <div class="row">
    <div class="col-md-6 col-md-offset-3">
      <div class="preview" style='overflow:hidden'>
          <div id="beforeUpload">
            <%= icon("fas fa-4x fw","file-image")%>
          </div>
          <img src="" id="croppedImage">
      </div>
      <%= form_with model: @user, url: users_path do |f| %>
        <%= f.file_field :image%>
        <%= f.hidden_field :x, id:"dataX"%>
        <%= f.hidden_field :y, id:"dataY"%>
        <%= f.hidden_field :width, id:"dataWidth"%>
        <%= f.hidden_field :height, id:"dataHeight"%>

        <%= f.submit "button", id:"btnUpload", class: 'btn btn-success'%>
      <% end %>
      <div class="modal fade" id="cropModal" role="dialog">
        <div class="modal-dialog">
          <div class="modal-content">
            <div class="modal-body">
              <div class="img-container">
                <img src="" id="imageModal" alt="picture">
              </div>
            </div>
            <div class="modal-footer">
              <button type="button" class="btn btn-primary" id="btn-save" data-dismiss="modal">Save</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<div class="preview" style='overflow:hidden'>でトリミング後の画像を表示するフィールドを作成しています。。この時、style='overflow:hidden'を指定しないと上手く表示されない場合があります。
トリミング後の画像は<img src="" id="croppedImage">で表示します。

form_with でフォームを作成し、トリミングした結果のパラメータはf.hidden_fieldでcontrollerに渡して、以下のshow.html.erbで画像を表示する際に用います。

<div class="modal fade" id="cropModal" role="dialog">以下ではトリミング操作を行うためのmodalをbootstrapによって表示させています。

トリミングした画像を表示するために以下のhtmlを作成します。
show.html.erb
<%= image_tag @user.image.variant(crop: "#{session[:crop_width]}x#{session[:crop_height]}+#{session[:crop_x]}+#{session[:crop_y]}") %>

では、トリミング処理を行うために以下のjavascriptファイルを作成します。

crop_image.js
$(function () {
  let $image = $('#imageModal'),
    $img_field = $('#user_image'),
    $croppedImage = $('#croppedImage'),
    $cropModal = $('#cropModal'),
    $beforeUpload = $('#beforeUpload'),
    $button = $('#btn-save'),
    $dataX = $('#dataX'),
    $dataY = $('#dataY'),
    $dataWidth = $('#dataWidth'),
    $dataHeight = $('#dataHeight');

  let options = {
    dragmode: 'crop',
    aspectRatio: 1/1,
    restore: false,
    guides: false,
    center: false,
    highlight: true,
    cropBoxMovable: true,
    cropBoxResizable: true,
    modal: true,
    crop: (e) => {
      $dataX.val(Math.round(e.detail.x));
      $dataY.val(Math.round(e.detail.y));
      $dataWidth.val(Math.round(e.detail.width));
      $dataHeight.val(Math.round(e.detail.height));
    }
  };

  // when file upload
  $img_field.change((e) => {
    $image.cropper('destroy').removeAttr('src');
    file = e.target.files[0];
    reader = new FileReader();
    if (file.type.indexOf('image') < 0) {
      window.alert("画像を選択してください");
      return ;
    }
    reader.onload = ((e) => {
      $image.attr('src',"");
      $image.attr('src', e.target.result);
      $cropModal.modal('show');
      $cropModal.on('shown.bs.modal', () => {
        $image.cropper(options);
      });
    });
    reader.readAsDataURL(file);
  })
  // onclick save button
  $button.click(() => {
    imgCropping();
  });

  // modalを閉じたとき、cropper要素を初期化
  $cropModal.on('hidden.bs.modal',function() {
    $image.cropper('destroy').removeAttr('src');
    let $cropperContainer = $('.cropper-container');
    $cropperContainer.remove();
  });

  function imgCropping() {
    if (!croppable) {
      alert('トリミングする画像が用意されていません')
      return false;
    }
    $beforeUpload.hide();
    let croppedData = $image.cropper('getCroppedCanvas').toDataURL();
    $croppedImage.attr('src', croppedData);
    $cropModal.modal('hide');
  }
});

$img_fieldでidがuser_imageのDOMを指定していますが、html側にはそのようなidはありません。Railsはform_withでfile_fieldを作成すると、自動的に<img id="モデル名_フィールド名">のタグが作成されるため、注意が必要です。自分もこれが原因でハマってしまいました...

optionsではどのように画像をトリミングするか設定しています。
$img_field.change以下で画像がアップロードされた時にトリミングが開始されるように記述しています。
modalが表示された際、$image.cropper(options)で先ほど設定したoptionsでcropperインスタンスを作成し、トリミングを行います。また、最初に$image.cropper('destroy')でcropperインスタンスを初期化していることに注意してください。この処理を行わないと、前にトリミングした画像が残ったままとなり画像が複数表示されたりといった不具合が起こります。

また、cropperインスタンスが作成された時点で、<div class="cropper-container cropper-bg"></div>のDOMが作成されます。modalを閉じた時点でこのDOMを削除しておかないと前回トリミングした画像が残ったままになっていることがあります。そのため$cropModal.on('hidden.bs.modal'以下で削除する操作を行っています。

以上です。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

#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で続きを読む

dockerでERROR: for mysql Cannot start service mysql: driver failed programming external connectivity on endpoint test-mysqlが出た時の対処

docker-compose run web rails new . --force --database=mysql --skip-bundleを実行するとエラーが出る

dockerでrailsの環境構築をしている中でいざ、railsのアプリを作成しようと以下のコマンドを実行
docker-compose run web rails new . --force --database=mysql --skip-bundle

そうすると以下のようなエラーが出る。
ERROR: for mysql Cannot start service mysql: driver failed programming external connectivity on endpoint test-mysql (b2c5bfe3339ba18ad0892ba8fc5a1f427a89b3915035bc12b0c4c1207e016f75): Error starting userland proxy: Bind for 0.0.0.0:3306 failed: port is already allocated
ERROR: Encountered errors while bringing up the project.

ようはそのポートはもう割り振ってしまっていますよって事ですね。
そのためポートを一度削除する必要があります。

まずは占領しているポート番号を特定します。

ポート番号を特定したら以下のコマンドを実行
$ sudo lsof -i:3300 *ここではポート番号3300にしています。

Password:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
mysqld 94 _mysql 13u IPv6 0x6973239584594d63 0t0 TCP *:mysql (LISTEN)

上の94という番号を今度はkillします。
sudo kill 9447

そうしてもう一度
docker-compose run web rails new . --force --database=mysql --skip-bundle
を実行すれば完了です。

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