- 投稿日:2020-10-25T23:59:44+09:00
意外と簡単にできた。パスワードを入力させずにユーザー情報を更新する方法
概要
今回の記事は表題の通り「パスワードを入力させずにユーザー情報を更新する方法」です。
Railsでとあるアプリケーションを作成中に、どうにかしてパスワードを入力させずにユーザー情報を更新できないものかと考えました。
updateメソッドではパスワードの入力を必須としているようです。
で、よく考えてみたらSNSやECサイトなどの多くのWEBアプリケーションは、ユーザー名やプロフィールなどをパスワードを入力せずとも更新可能です。
updateメソッドをオーバーライドとかしなくても、意外と簡単に出来るのでは...と思い調べてみました。実際にやったこと
update_without_passwordという、そのまんまな名前をしているメソッドがありました。
これを用いて今回は下記のように実装しました。
単純にパラメータに含まれるパスワードが空かどうかで、使用するメソッドを振り分けています。# パラメータに含まれるパスワードが空の場合、 if params[:password].blank? # パスワードなしでユーザー情報を更新 @user.update_without_password(user_params) # パラメータにパスワードが含まれていた場合 else @user.update(user_params)注意点
モデルに以下のような記載があると、update_without_passwordメソッドをつかった際にエラーになります。
パスワードの入力を必須にする、という意味のバリデーションですね!app/models/user.rb
validates :password, presence: trueonオプションでバリデーションをかけるアクションを指定しましょう。
以下のようにすれば、ユーザーを作成する際のみバリデーションがかかります。validates :password, presence: true, on: :create
- 投稿日:2020-10-25T23:26:48+09:00
[Rails]Modelの関連付け(アソシエーション)
Railsのモデルの(アソシエーション)の種類の忘備録です
参考資料:Railsガイド
https://railsguides.jp/association_basics.html関連付け(アソシエーション)を行う理由
2つのActive Recordモデルの繋がりを関連付け(アソシエーション)という。
以下、アソシエーションは関連付けでと統一して記述していく。
関連付けをする理由は
・モデル間の共通操作を可能とし、コードの記述がシンプルで簡単になる
・上記により、コードの見通しが良くなる
為である。
※主キー、外部キーの詳細は省く。Example
簡単なタスク管理アプリを例にして、ユーザー(User)とタスク(Task)の関連付けを書いていく。(Railsガイドの沿って記述していく)
class User < ApplicationRecord end class Task < ApplicationRecord endユーザーは新しいタスクを追加する場合とユーザーを削除する場合を関連付け無しで記述すると以下のような実装となると思われる。
#新しいタスクの追加 @task = Task.create(task: "買い物", user_id: @user.id) #ユーザーの削除(この場合、削除されるユーザーのタスクも一緒に全て削除しなければ、いつまでもDBに意味のないデータが残されてしまう) @tasks = Task.where(user_id: @user.id) @tasks.each do |task| task.destroy end @user.destroyRailsのモデルに明示的に関連付けを追加することで、より簡潔にコードを記述することが可能となる。
まず、モデルの関連付けを定義する。class User < ApplicationRecord #ユーザーは複数のタスクを持つよ, ユーザーが削除されたら、tasksも全て削除してねと定義 has_many :tasks, dependent: :destroy end class Task < ApplicationRecord #タスクは1人のユーザーから生み出されるよ belong_to :user endこれで関連付けは完了である。
has_manyとbelong_toを、かなりざっくりと解説するとUserから見ると、Task_a,Task_b,Task_cと複数タスクを持てるけど、
Taskから見ると、Userは1人しかいない
1(User)対多(Task)の関係だとRailsに定義した。ちなみに、dependent: :destroyオプションは、
ユーザーが削除されたら、そのユーザーのタスクも漏れなく全て削除してねという意味である。上記のように関連付けを行ったことにより、
新しいタスクの追加とユーザーの削除は下記のよう簡潔に記述できるようになった。#新しいタスクの追加 @task = @user.tasks.create(task: "買い物") #ユーザーの削除(dependent: :destroyオプションでユーザーのタスクも一緒に削除される。) @user.destroy特に削除の部分は5行が1行で済む。
コードを見ても、すぐに何をしているのか理解が可能で見通しも良くなった。関連付けの種類
belongs_to
has_one
has_many
has_many :through
has_one :through
has_and_belongs_to_many説明
belong_to
1対1の関連付けが設定される。
宣言を行ったモデルのすべてのインスタンスは、他方のモデルのインスタンスに「従属(belongs to)」する。
Exampleの章を例にすると1つのTaskに対して1人のユーザーを割り当てる関係を表現している。
belongs_to関連付けで指定するモデル名は必ず「単数形」にしなければならない。#ユーザーは1人しかいない為、単数形出なければならない。 #Railsの自動推測でエラーとなる。 class Task < ApplicationRecord belong_to :user endhas_one
1対1の関連付けが設定される。belong_toとの違いは、
宣言が行われているモデルのインスタンスが、他方のモデルのインスタンスを「まるごと含んでいる」または「所有している」ことを示す。
国民健康保険の保険証を例にとる(分かりづらい(笑)?)1人の人が1つの保険証を所有している。 class people < ApplicationRecord has_one :insurance_card end 1人の人に保険証は所有されている。 class insurance_card < ApplicationRecord belong_to :people endhas_many
「1対多」のつながりがあることを示す。
has_many関連付けが使われている場合、そのモデルのインスタンスは、反対側のモデルの「0個以上の」インスタンスを所有する。
Exampleの章を例にすると1人のユーザーが複数のタスクを持っている関係を表現できる。
has_many関連付けを宣言する場合、相手のモデル名は「複数形」にする必要がある。#ユーザー1人に対して、タスクは複数所有できる為、複数形にて記述しなければならない。 #Railsの自動推測でエラーとなる。 class User < ApplicationRecord has_many :tasks endhas_many :through
「多対多」のつながりを設定する場合によく使われる。
この関連付けは、2つのモデルの間に「第3のモデル(中間モデル)」を作成する。それによって、相手モデルの「0個以上」のインスタンスとマッチする。
複数の授業と複数の生徒から、特定の授業に出る生徒を限定することが可能となる。生徒は、複数の授業を受けている。 class Student < ApplicationRecord has_many :members has_many :class_works, through: :members end 複数の授業と、複数の生徒のidを保存することで、特定の授業に出てる生徒を限定することが出来る。 class Member < ApplicationRecord belongs_to :student belongs_to :class_work end 授業は複数の生徒が受けている。 class Class_work < ApplicationRecord has_many :members has_many :students, through: :members endhas_one :through
「1対1」のつながりを設定する。
この関連付けは、2つのモデルの間に「第3のモデル(中間モデル)」を作成する。
それにより、相手モデルの1つのインスタンスとマッチする。
思いつかないのでRailsガイドをそのまま例にとる。1人の提供者(supplier)が1つのアカウントに関連付けられ、さらに1つのアカウントが1つのアカウント履歴に関連付けられる場合、supplierモデルは以下のような感じになります。
#Supplierはaccountを持ち、accountを通じてaccout_historyを持つ。 class Supplier < ApplicationRecord has_one :account has_one :account_history, through: :account end #accountはsupplierに属していて、account_historyを一つ持つ class Account < ApplicationRecord belongs_to :supplier has_one :account_history end #AccountHistoryはaccoutに属している class AccountHistory < ApplicationRecord belongs_to :account endhas_and_belongs_to_many
「多対多」のつながりを作成する。しかしthrough:を指定した場合と異なり、第3のモデル(中間モデル)がない。
完成車(assembly)と部品(part)があり、1つの完成車に多数の部品が対応し、逆に1つの部品にも多くの完成車が対応するのであれば、モデルの宣言は以下のようになります。#完成車はたくさんの部品(parts)が取り付けられて車となる。 class Assembly < ApplicationRecord has_and_belongs_to_many :parts end #部品は複数あり、複数のの完成車に取り付けられている。 class Part < ApplicationRecord has_and_belongs_to_many :assemblies end
- 投稿日:2020-10-25T23:05:03+09:00
プログラミング学習歴1ヶ月半からWebアプリを開発してみた
1.はじめに
2020年8月からプログラミング勉強を勉強していますが、約1ヶ月かけてWebアプリを開発しました。勉強する前はこんな状態でした。
・HTML、CSSはうっすらと名前を聞いたことがある
・現職ではWord、Excelぐらいしか使用していない
・寿司打で3000円コースすらクリアできないこの状態から1ヶ月半学習しほぼ独学でWebアプリを開発をしました。
(一応10月から侍エンジニア塾にレッスンを受けていますが、アプリ開発前の基礎的な部分は独学で学習したこと、アプリ開発においては技術的な質問の回答、コードのレビューをインストラクターにしていただきましたが、アプリの方向性など大半は独自で考えたことを考慮してほぼ独学という言葉を使いました。)
学習歴1ヶ月半の実力を記録として残したくて記事にしました。
2.自己紹介
年齢:32歳
職業:プラント設計
未経験からエンジニア転職目指して勉強中。
2020年4月コロナ禍の中、一児の父となる。生まれた直後2ヶ月間、育児休暇を取得。
プログラミング学習より育児を優先している。離乳食の裏ごしで腕を痛める。
学習履歴
2020年8月1日~9月18日
Progate : HTML,CSS,JavaScript,Ruby,Rails,Command Line,Git,SQL (各2週ずつ)
プロを目指す人のためのRuby入門(第5章ぐらいまで)
Udemy: Git:はじめてのGitとGitHub、米国AI開発者がゼロから教えるDocker講座
9月19日〜 Webアプリ作成開始3.アプリ概要
育児の悩み、不安を救うための育児特化型のQ&Aアプリです。
Herokuで公開しています→ーSUKUSUKUー(PC&Chrome推奨)
Github → https://github.com/SHOGOHORI/myapp
使い方
ログインなしでも、質問一覧、質問詳細の閲覧、キーワード検索、タグ検索が可能
ゲストユーザーで簡単ログイン(
ゲストユーザーとしてログイン
ボタンをクリック)
質問を投稿する
ボタンをクリックし、投稿フォーム画面へ質問詳細から回答可能
4.作成目的
長男が生まれた直後、妻が感染症により高熱を出し、入院する、しないの騒ぎになりました。結局入院はしませんでしたが1〜2週間ほどずっと熱は下がらず寝たきりになり、私一人で育児をしていました。
生まれたばかりの一つ命を預かるという、とてつもない責任を感じていました。孤独と不安で押しつぶされそうな状況でした。その時の経験から、同じような状況にいるママ、パパの不安を少しでも埋めたいという思い、SNSに近い形のQ&Aアプリを作成しました。作成期間
2020年9月19日〜10月20日(31日間)
一日6〜8時間ぐらい作成に費やしていましたので、作成時間は180〜200時間ぐらいです。
5.設計
ER図
サイトマップ
ワイヤーフレーム
6.使用技術
フレームワーク:Rails6.0.3
フロント:HTML、Sass、JavaScript(jQuery)
サーバーサイド言語:Ruby2.7.1
データベース:PostgreSQL
テストフレームワーク:Minitest → Rspec
サーバー構築:puma
開発環境:Docker 19.3.12 → ローカル環境
本番環境:Heroku
検索機能:ransack
バージョン管理:Git hub
ページネーション機能:kaminari
使用マシン:Mac Catalina(10.15.7)
エディタ:VSCode7.機能
ユーザー機能
・新規登録、ログイン、ログアウト、ゲストユーザーログイン機能
・ログイン保持機能
・ユーザープロフィール編集機能
・ユーザーマイページが投稿した質問、回答の一覧表示投稿機能
・一覧表示、記事詳細表示、投稿、画像アップロード、編集、削除機能
・回答投稿機能質問検索機能
・キーワード検索、タグ検索機能
ページネーション機能
・Ajax対応
テスト機能
・単体、統合テスト機能
8.苦労した点
Dockerによる開発環境構築
当初開発環境にDockerを導入しましたが、非常に重く、rails db:migrateやテストを実行する際にすごく時間がかかってしまいました。軽くする方法を色々調べましたが、状況は改善されず結局ローカル環境に切り替えました。
後述するVer.2ではもう一度チャレンジしたいです。RSpecによるテスト記載
RailsチュートリアルではMinitestを使用していたので、RSpecについて勉強しました。Rspecの文法理解、Capybaraの使い方等、苦労しました。
苦労した経験のアウトプット→RSpecのディレクトリ構成とspecごと役割について
Ajax対応
JavaScriptの知識が不足していたのと、そもそもAjaxがどういう仕組なのか理解していなかった為、そもそもAjaxとは?JavaScriptでなにができるのか?jQueryの基本的な書き方・文法から勉強しました。
苦労した経験のアウトプット→【Rails】ページネーションをAjax対応にする
ビューのコーディング
フロント部分、デザインとHTML、Sass、Bootstrap使い方について学習していなかったため、苦労しました。
9.反省点
Railsチュートリアルを起点にアプリ開発をスタートした
- Railsチュートリアルの復習という意味で勉強になりましたが、必要ない機能も追加してしまいました。(ユーザー登録にメールを使用する機能、パスワード再設定機能)
解決策:要件定義の段階で必要な機能を洗い出す。
- Railsチュートリアルで使用しているGemしかよく知りませんでした。(アプリ作成当初、devise、kaminari、carrierwave、RSpec、RuboCop等よく使われるGemについて知らなかった)
解決策:必要な機能に対してどのGemが適切か選定する。選定の際に評価と更新履歴を確認する。
- レイアウトにRailsチュートリアル感が出てしまいました。
解決策:ワイヤーフレーム作成の段階でどの層に向けてのWebアプリなのか意識してデザインする。
計画性がなかった
- 要件定義を厳密にすると時間がかかると考え、簡単なER図とワイヤーフレームを作成した後、とりあえず手を動かしながら場当たり的に計画していったことから、ゴールがよくわからなくなってしまいました。逆に時間がかかりすぎたように思います。
解決策:まず作成完了の期限を決める。機能ごとにGithubのissueを作成して、branchをきる。
コードが煩雑になってしまった
- コードをインストラクターにレビューしてもらった際、trailing whitespace(末尾のスペース)や無駄な改行、無駄なファイルが散見されました。
解決策:開発前にVScodeをコーディング規約の守れる設定にする。RuboCopで細かく静的コード解析を実行する。こまめに使用していないファイルを調べ削除する。
ただのQ&Aサイトになってしまった
- 初めて独自のアプリを作るため、挫折しないよう機能は最小限にとどめました。その結果、あまり独自性のないアプリになってしまいました。
解決策:後述するリーンキャンバスを作成し、同じテーマの他のサービスとの独自性を図る。
10.Ver.2に向けて
別のWebアプリを作成することも考えましたが、子育てしている経験をWebアプリ開発に活かしたいのと、「新米ママ、パパの不安を少しでも埋めたい」という思いから、今のアプリをブラッシュアップしたVer.2を作成したいと思います。今回の反省を活かし、Ver.2でやりたいことをまとめました。(全部できるかわかりませんが。。。)
要件定義を具体的に行う
Ver.1を作成して、計画性、サービスの独自性、デザイン等全ておいて要件定義の重要性を認識しました。Ver.2ではリーンキャンバスを利用して要件定義を具体的に行おうと思います。そして、あの技術を使いたい!よりも、この要件を満たすためにはこの技術が必要、という形で技術を選びたいです。
また、現職では設計の工程管理をしていたので、その強みを生かして開発の工程表を作成したいです。実務を意識してGit、Githubを使用する
Ver.1ではbranchをきって、git pushするぐらいしかしていなかったので、GithubのissueやGit-flowの活用、疑似共同開発を意識してプルリクしたりと、実務を意識してGit、Githubのバージョン管理をしたいです。
Git-flowって何?
GitHub Cheat Sheet(日本語訳)AWSへデプロイ、CI/CDパイプラインの構築
実際に運用することを想定してVer.2を作成したいので、AWSへデプロイしてドメイン取得までしたいです。あとネットワークの勉強も兼ねて。
また、後の作業効率を考えてCI/CDパイプラインの構築したいです。想定する顧客や専門家に話を聞く
新米ママ、パパに実際にサービスを使用してもらって、ヒヤリングしてブラッシュアップしたり、専門家に意見を聞いたりしたいです。
その他実装したい機能
・ユーザー登録にdeviseを使用
・パフォーマンスを考慮し、テンプレートエンジンをhamlかslimにする
・マイページに子ども情報(年齢、性別)を追加
・回答の返信機能
・ミニ日記機能
・ブックマーク機能
・フォロー、アンフォロー機能
・アンケート機能
・SNSシェア機能
・デザインを充実させる
・データベースをMySQL11.まとめ
Webアプリを開発してみて、下記のことがわかりました。
・よく言われる通り、アウトプットしつつのインプットが成長につながる
・1つのエラーに何時間も費やす経験も良かったが、質問できるような環境構築も大事
・要件定義に時間をかけることの重要性この経験を今後に活かしたいです。
何かWebアプリに問題点、指摘がありましたらどしどしコメントください!
- 投稿日:2020-10-25T22:52:21+09:00
【Rails】amCharts4を用いたグラフ描画における第2縦軸の作成及び日本語化 他
はじめに
本記事では、JavaScriptのグラフ描画ライブラリのamChartを用いて、複数軸の線グラフの実装する際の第2縦軸の作成方法や日本語化などのカスタム方法を共有します。
開発環境
Ruby 2.5.1
Rails 5.2.4.4amChartsとは
amChartsは、javascriptのグラフ描画ライブラリで、様々な種類の高機能なグラフを描画することができます。
公式リファレンス:https://www.amcharts.com/docs/v4/
日本語の情報が少ないため、カスタマイズする際は、公式リファレンスを参照することをおすすめします。
完成イメージ
現在、体重と体脂肪率を記録してグラフ化する機能を持つアプリを開発中で、
1つのグラフに横軸を日付、第1縦軸に体重、第2縦軸に体脂肪率を描画するため、amCharts4を使用しました。
- 横軸:日付、第1縦軸:体重、第2縦軸:体脂肪率
- カーソル上のデータをTooltipで表示
- スクロールバーで拡大
- タイトルをマウスホバーするとTooltipを表示
実装手順/解説
amChartsの導入
amChartsの導入方法はこちらの記事が参考になります。
amcharts 4 Demos を使ってグラフを作成横軸の値が不連続な場合のグラフの作成
本記事の横軸の値が不連続になる場合のグラフの作成は、下記の記事を参考にさせていただきました。
Railsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。デモデータの準備
csvファイルをseedして以下のようなデモデータを準備します。
id date weight body_fat_percentage 1 2020/06/08 72 15 2 ・・・ ・・・ ・・・ 完成サンプルコード
解説の前にサンプルコードを貼っておきます。
Rails側の記述は今回割愛します。
record.html.erb
<style> #chartdiv { width: 700px; height: 300px; } </style> //必要なJSファイルの読み込み <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> <script src="//www.amcharts.com/lib/4/lang/ja_JP.js"></script> <script> am4core.ready(function() { am4core.useTheme(am4themes_animated); var chart = am4core.create("chartdiv", am4charts.XYChart); chart.dateFormatter.language = new am4core.Language(); chart.dateFormatter.language.locale = am4lang_ja_JP; chart.language.locale["_date_day"] = "MMMdd日"; chart.language.locale["_date_year"] = "yyyy年"; const weights = <%== JSON.dump(@weights) %>; const body_fat_percentages = <%== JSON.dump(@body_fat_percentages) %>; 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 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); if ((new Date(dates[j])) - (newDate) == 0 ){ weight = weights[j] body_fat_percentage = body_fat_percentages[j] chartData.push({ date1: newDate, weight: weight, date2: newDate, body_fat_percentage: body_fat_percentage }); } } } return chartData; } chart.data = generateChartData(); //グラフタイトルの設定 var title = chart.titles.create(); title.text = "体重・体脂肪率の推移"; //グラフタイトルの設定 title.fontSize = 15; //グラフタイトルのフォントサイズの設定 //タイトルをマウスホバーした際に表示させるTooltipの表示内容設定 title.tooltipText = "スクロールバーで拡大できます。"; //第1横軸の設定 var dateAxis = chart.xAxes.push(new am4charts.DateAxis()); dateAxis.renderer.grid.template.location = 0; dateAxis.renderer.labels.template.fill = am4core.color("#ffffff"); //第2横軸の設定 var dateAxis2 = chart.xAxes.push(new am4charts.DateAxis()); dateAxis2.tooltip.disabled = true; //Tooltipの非表示設定 dateAxis2.renderer.grid.template.location = 0; dateAxis2.renderer.labels.template.fill = am4core.color("#000000"); //第1縦軸の設定 var valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); valueAxis.tooltip.disabled = true; valueAxis.renderer.labels.template.fill = am4core.color("#e59165"); valueAxis.renderer.minWidth = 60; valueAxis.renderer.labels.template.adapter.add("text", function(text) { return text + "kg"; }); valueAxis.renderer.fontWeight = "bold"; //軸の値を太字に変更 //第2縦軸の設定 var valueAxis2 = chart.yAxes.push(new am4charts.ValueAxis()); valueAxis2.tooltip.disabled = true; valueAxis2.renderer.grid.template.strokeDasharray = "2,3"; valueAxis2.renderer.labels.template.fill = am4core.color("#dfcc64"); valueAxis2.renderer.minWidth = 60; valueAxis2.renderer.labels.template.adapter.add("text", function(text) { return text + "%"; }); valueAxis2.renderer.opposite = true; //第2縦軸を右側に設定 valueAxis2.renderer.fontWeight = "bold"; //軸の値を太字に変更 //第1縦軸用の値の設定 var series = chart.series.push(new am4charts.LineSeries()); series.name = "体重"; series.dataFields.dateX = "date1"; series.dataFields.valueY = "weight"; series.tooltipText = "{valueY.value}kg"; series.fill = am4core.color("#e59165"); series.stroke = am4core.color("#e59165"); series.smoothing = "monotoneX"; series.strokeWidth = 2; //系列のポイントの設定(第1縦軸) var bullet = series.bullets.push(new am4charts.Bullet()); var circle = bullet.createChild(am4core.Circle); circle.width = 5; circle.height = 5; circle.horizontalCenter = "middle"; circle.verticalCenter = "middle"; //第1縦軸用の値の設定 var series2 = chart.series.push(new am4charts.LineSeries()); series2.name = "体脂肪率"; series2.dataFields.dateX = "date2"; series2.dataFields.valueY = "body_fat_percentage"; series2.yAxis = valueAxis2; series2.xAxis = dateAxis2; series2.tooltipText = "{valueY.value}%"; //ツールチップの表示設定 series2.fill = am4core.color("#dfcc64"); //ツールチップの色 series2.stroke = am4core.color("#dfcc64"); //グラフの線の色 series2.smoothing = "monotoneX"; series2.strokeWidth = 2; //系列のポイントの設定(第2縦軸) var bullet2 = series2.bullets.push(new am4charts.Bullet()); var circle2 = bullet2.createChild(am4core.Circle); circle2.width = 5; circle2.height = 5; circle2.horizontalCenter = "middle"; circle2.verticalCenter = "middle"; chart.scrollbarX = new am4core.Scrollbar(); //スクロールバーの設定 //カーソルの設定 chart.cursor = new am4charts.XYCursor(); chart.cursor.xAxis = dateAxis2; //凡例の設定 chart.legend = new am4charts.Legend(); chart.legend.parent = chart.plotContainer; chart.legend.zIndex = 100; chart.legend.position = "top"; chart.legend.contentAlign = "right"; //グリッド線の設定 valueAxis2.renderer.grid.template.strokeOpacity = 0.07; dateAxis2.renderer.grid.template.strokeOpacity = 0.07; dateAxis.renderer.grid.template.strokeOpacity = 0.07; valueAxis.renderer.grid.template.strokeOpacity = 0.07; }); </script> <div id="chartdiv"></div>解説
複数縦軸の設定
サンプルコードの通り、複数軸のグラフの場合、各軸及び各値の設定が必要になります。
それぞれの使用するデータなどの設定を行います。
- 第1横軸:dateAxis
- 第2横軸:dateAxis2
- 第1縦軸:valueAxis
- 第2縦軸:valueAxis2
- 体重:series
- 体脂肪率:series2
日本語化
横軸が日付の場合、デフォルトの表記が米国式のため下図のように英語表記になります。
そのままでも問題は無いのですが、もし「◯月◯日」という表記にしたい場合は、以下の設定を追加します。
標準の翻訳設定では、例えば「Aug」を「8月」に翻訳はできますが、何日の方は「〇〇日」とは翻訳されないため、独自ルールを以下のように追加します。年も同様に行えます。<script src="//www.amcharts.com/lib/4/lang/ja_JP.js"></script> //localeファイルの呼び出し <script> //中略 chart.dateFormatter.language = new am4core.Language(); //標準の翻訳設定 chart.dateFormatter.language.locale = am4lang_ja_JP; //標準の翻訳設定 chart.language.locale["_date_day"] = "MMMdd日"; 独自ルールで上書き chart.language.locale["_date_year"] = "yyyy年"; 独自ルールで上書き //中略 </script>【参考リンク】
https://www.amcharts.com/docs/v4/concepts/locales/
https://github.com/amcharts/amcharts4/blob/master/src/lang/ja_JP.ts第2縦軸の設定
デフォルトの設定ですと第1縦軸と第2縦軸は両方左側にあります。
少し見にくいので、第2縦軸を右側に変更したい時は、以下の設定を追加します。valueAxis2.renderer.opposite = true; //第2縦軸を右側に設定他の追加設定を紹介
- データポイントの設定
各系列毎にデータポイントの設定が行えます。circleをsquareに変えると四角に変更できます。
var bullet = series.bullets.push(new am4charts.Bullet()); var circle = bullet.createChild(am4core.Circle); circle.width = 5; circle.height = 5; circle.horizontalCenter = "middle"; circle.verticalCenter = "middle";【参考リンク】
https://www.amcharts.com/docs/v4/concepts/bullets/
- グラフの線を曲線に変更
series.smoothing = "monotoneX";
- 色の変更
series2.fill = am4core.color("#dfcc64"); //ツールチップの色 series2.stroke = am4core.color("#dfcc64"); //グラフの線の色まとめ
amChartsを使うと高機能なグラフを描画できます。
折れ線グラフ以外にも様々なグラフを作ることができます。
日本語の情報が少ないので、カスタマイズしたい場合は、公式リファレンスを参照することをおすすめします。参考URL
amcharts 4 Demos を使ってグラフを作成
Railsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。
- 投稿日:2020-10-25T21:03:26+09:00
問題解決するには2つの記事を参考にすると解決できる!!!
どうも、三町哲平です!!
プロミングをしているととにかく分からない事が出まくりますよね!?
初めて行う実装やエラー解決の為にあれやこれやすると思いますが、そういう時は、本で調べたり、人に聞いたりすることがあるでしょうが、やっぱり一番するのって、ググって調べる事じゃないでしょうか...?少なくとも私は9割以上の問題に対し、ググって解決しています。
しかし...しかしですよ。
例えばRuby on Railsで出た問題に対してあれやこれや調べたとしても開発環境が違ったり、データベースが違ったり、バージョンが違ったり、アプリ名が違ったりと全く一緒の条件でアプリ開発している可能性なんて限りなく0です。必ずどこかに違いがあります。
そんな微妙な違いによってただコピペしただけだは、中々実装できなかったり、エラー解決できなかったりして最終的には嫌になって止めてしまう...。
そうならない為にも問題に対峙する時にこれに気を付ければ上手く解決できるかもね...というやり方を一つ見つけたので、そのご紹介です。
問題解決するには2つの記事を参考にすると解決できる!!!
タイトル通りなのですが、答えは、
「問題解決するには2つの記事を参考にすると解決できる!!!」です。
もうこれが今回の結論なのですが、これだけじゃ分かりにくいので参考例をどうぞ!1. やり方が分からない...。
まず、ググって調べるに当たって直面している壁といったらとにかくやり方が分からないって所です。
- 何で、このエラーは出たのだろう?
- どうやってこの機能を実装すればいいのだろう? など、など...
その疑問に対して調べまくった結果、訳わからんって状態になる中で今回は、ja.ymlの書き方が分からないという問題の解決方法を模索していました。
状態としては、投稿フォームで、紹介文というテキスト欄が空欄だった場合に表示するエラーメッセージを全て日本語表示にしたい。
つまり、Contentを入力してくださいのContentを日本語にしたいという話です。
ちなみに、Contetは保存したいデータベース(postテーブル)のカラム名になります。
2. ググって行き着いた記事
ググると沢山似たような記事が検索に引っかかる事があります。その中で自分自身にとって分かりやすく、状況が似ている記事を参考にしていく中で私は、
Railsのバリデーションエラーのメッセージの日本語化 - Qiita
この記事を参考にさせて頂きました。
参考にした結果↑この様にja.ymlを作成して、入力しました。これは、Railsのバリデーションエラーのメッセージの日本語化 - Qiita
のカラム名の日本語化のコードをコピペしただけです。この結果が実は先ほどお見せした投稿フォームの画像になります。↓再掲しています。
まあ...つまりは、コードをコピペしただけでは、今回のカラムを日本語化したいという問題は、解決しなかった訳ですね。
3. ja.ymlの書き方が分からないからまたググってみる
ja.ymlの何処かが間違っている...何が違うんだ...!?
そんな疑問の中、エラーメッセージの日本語化を再度調べていく中で、
ActiveRecordのvalidatesで表示されるエラーメッセージのフォーマットを変更する - Qiita
こちらの記事を発見!!そして、ja.ymlで、
config/local/models/ja.ymlja: activerecord: models: user: ユーザー attributes: user: name: 名前↑上記のコードを発見!
config/local/models/ja.ymlja: activerecord: models: event: イベント attributes: event: name: イベント名 place: 開催場所 content: イベント内容↑これが現在使用しているコードです。
ここで、タイトルにもどります。
問題解決するには2つの記事を参考にすると解決できる!!!
に戻ります。2つのコードを見ている中で、これって、
config/local/models/ja.ymlja: activerecord: models: user: ユーザー attributes: user: name: 名前↑このコードだと、user:が、
config/local/models/ja.ymlja: activerecord: models: event: イベント attributes: event: name: イベント名 place: 開催場所 content: イベント内容↑このコードだと、event:の部分が、テーブル名だと気づいた私は...
[Before]
↓
[After]
この様に変更して...無事、エラーメッセージを全て日本語化できました!!
結果
繰り返しになりますが、
「問題解決するには2つの記事を参考にすると解決できる!!!」はこれにて完了です。...ご参考までに。
- 投稿日:2020-10-25T21:02:16+09:00
mergeメソッドについて改めて理解を深めた
はじめに
formオブジェクトを用いて、フォームから複数のテーブルに情報を保存する機能を実装する過程で、いちばん悩んだエラーについて、忘れないために記録しておく。
想定している場面
ユーザーが商品を購入する。formに入力したものをデータベースに保存すると同時に、どの商品をどのユーザーが購入したかも保存する。つまり、フォームで「購入」を押した時に、2つのテーブルに保存される。
mergeメソッドについて
ストロングパラメーターを設定するときに、使うメソッド。
使用例
controllers.rbprivate def user_order_params params.require(:user_order).permit(:postal_code, :prefecture_id).merge(user_id: current_user.id, item_id: params[:item_id]) end
user_id: current_user.id
のcurrent_userメソッドが使えるのは、deviseのGemを導入しているため。
item_id: params[:item_id]
で値を入れることができるのは、ルーティングをネストし、URLにitem_id
を含めているため。
ストロングパラメーターはprivateメソッド以下に記述する。
require
の引数は、モデル名。
permit
の引数は、DBのカラム名。ターミナルで確認できるパラメーター
"user_order"=>{"hoge"=>"", "postal_code"=>"", "prefecture_id"=>"1"}, "commit"=>"購入", "controller"=>"orders", "action"=>"create", "item_id"=>"7"}mergeメソッドを使う場面
form_withでユーザーが記入した内容はハッシュの中に、キーと一緒に入っているが、ユーザーが記入しない内容も保存したい時。
例えば、ユーザーのidやその商品のidについては、ユーザーが直接入力することはないが、パラメーターに含めて、DBの保存したい。そのような時に、mergeメソッドを使って、パラメーターに含めたいキーと値を記述する。
上記の例では、user_idをcrrent_user.idから、item_idをURLに含めたparamsから取ってきて、パラメーターに含めている。最後に
エラーが解決できた時、マージかぁと一人呟いたとさ…。
- 投稿日:2020-10-25T20:57:10+09:00
「API開発 + Swagger UIを利用したAPI検証」な環境をDockerで構築する
Swaggerを利用することでREST APIの仕様をドキュメント化できます。
SwaggerではREST APIの仕様をドキュメントしたファイルをSwagger Specと呼びます。Swagger UIとはSwagger Specの情報を反映させた静的ページを生成するツールのことを言います。
Swagger UIはSwagger Specの情報を可視化するだけでなく、画面上からREST APIを実行する機能も提供しています。今回は「開発中のAPIをSwagger Specでドキュメント化 → Swagger Specの情報が反映されたSwagger UIの画面からAPIリクエストの検証」という一連の作業が行えるDocker環境の構築手順について紹介します。
今回作成するDocker環境について
仕様は以下の通りです。
- docker-compose upだけで環境が準備できる
- 「/swagger-ui」でSwagger UIの画面が表示される
- 「/swagger-ui」以外はAPI用のエンドポイントとする
- Swagger Spec編集後、リロードでSwagger UIに変更内容が反映される
- APIはRuby on RailsのAPIモードで作成する
- DBはMySQLを利用
nginxをリバースプロキシとして利用することでAPIの開発環境とSwagger UIを組み合わせます。
図で表現すると以下のようになります。今回の利用する各種バージョンは以下の通りです。
- Ruby on Rails: 6.0.3.2
- Ruby: 2.7.1
- MySQL: 8.0.21
- nginx: 1.19.3
API開発環境をDockerに作成する
APIモードで作成する『Rails 6 x MySQL 8』Docker環境構築手順を参考に、RailsのAPIモードを利用してAPI開発環境を作成します。
Dockerfileとdocker-compose.ymlは以下の通りです。
DockerfileFROM ruby:2.7.1 # 作業ディレクトリを/rails_api_swaggerに指定 WORKDIR /rails_api_swagger # ローカルのGemfileをDokcerにコピー COPY Gemfile* /rails_api_swagger/ # /rails_api_swaggerディレクトリ上でbundle install RUN bundle installdocker-compose.ymlversion: '3' services: api: # Ruby on Railsが起動するコンテナ build: . ports: - '3000:3000' # localhostの3000ポートでアクセスできるようにする volumes: - .:/rails_api_swagger # アプリケーションファイルの同期 depends_on: - db command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"] db: # MySQLが起動するコンテナ image: mysql:8.0.21 volumes: - mysql_data:/var/lib/mysql # データの永続化 - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d command: --default-authentication-plugin=mysql_native_password # 認証方式を8系以前のものにする。 environment: MYSQL_USER: 'webuser' MYSQL_PASSWORD: 'webpass' MYSQL_ROOT_PASSWORD: 'pass' MYSQL_DATABASE: 'rails_api_swagger_development' volumes: mysql_data: # データボリュームの登録RailsのAPIモードでは静的ページを生成する機能は除外されているため、Swagger UIを直接Railsアプリケーションに組み込むことはできませんが、今回の方法を利用すればAPIモードでもSwagger UIを利用できます。
サンプルとなるAPIを作成します。
# コンテナをバックグランドで起動 $ docker-compose up -d # Eventを操作する機能(モデル、ビュー、コントローラー)を一括作成 $ docker-compose exec api rails g scaffold event title:string # eventsテーブルを作成 $ docker-compose exec api rails db:migrate # rails consoleでeventsのレコードを作成 $ docker-compose exec api rails c > event = Event.new(title: 'サンプルイベント') > event.save
localhost:3000/events
にアクセスして以下のようなレスポンスが返ってくればOKです。リバースプロキシの設定を行い、nginx経由でAPIにアクセスできるようにする
nginx経由でRailsアプリケーションにアクセスできるようリバースプロキシの設定を行ます。
default.confserver { listen 80; server_name localhost; # "/"にアクセスがあったときの処理 location / { proxy_set_header Host localhost; # アクセス元のホストをlocalhostにする proxy_pass http://api:3000; # apiコンテナの3000ポートにリクエストを送る } }nginxの設定ファイルは
/etc/nginx/nginx.conf
です。
設定ファイルにinclude /etc/nginx/conf.d/*.conf;
という記述があることからも分かるように、設定ファイルでは/etc/nginx/conf.d
配下の.conf
という拡張子の設定も読み込んでいます。つまり、
/etc/nginx/conf.d
配下に今回作成した設定ファイルを配置することで、nginxコンテナをリバースプロキシとして利用できます。docker-compose.ymlにnginxコンテナを追加します。
docker-compose.ymlservices: api: (略) db: (略) nginx: image: nginx:1.19.3 ports: - '80:80' command: [nginx-debug, '-g', 'daemon off;'] volumes: - ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf depends_on: - api volumes: mysql_data:
[nginx-debug, '-g', 'daemon off;']
はデバッグモードによる起動方法です。1なお、swagger-uiのDockerイメージもnginxを利用していますが、
nginx.conf
にinclude /etc/nginx/conf.d/*.conf;
の記述がありません。
ですので、swagger-uiのDockerイメージを利用する場合だと今回のアプローチはうまくいかないので注意してください。コンテナ起動後、
localhost:80/events
にアクセスして以下のようなレスポンスが返ってくればOKです。Swagger UIをnginxに組み込む
/swagger-ui
にアクセスをしたらSwagger UIの画面が表示されるようにnginxにSwagger UIを組み込んでいきます。Swagger UIの画面はswagger-ui/distによって構成されています。
dist
配下のファイルをローカルにコピーし、nginxコンテナにバインドマウントすることで、Swagger UIをnginxに組み込みます。
dist
配下のファイルをすべてコピーしてきてもよいのですが、unpkgを利用することでindex.html
のみをコピーするだけでSwagger UIの画面が作成できます。 2index.html<!-- HTML for static distribution bundle build --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Swagger UI</title> - <link rel="stylesheet" type="text/css" href="./swagger-ui.css" > + <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3/swagger-ui.css" > <style> html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; } *, *:before, *:after { box-sizing: inherit; } body { margin:0; background: #fafafa; } </style> </head> <body> <div id="swagger-ui"></div> - <script src="./swagger-ui-bundle.js" charset="UTF-8"> </script> + <script src="https://unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js" charset="UTF-8"> </script> - <script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script> + <script src="https://unpkg.com/swagger-ui-dist@3/swagger-ui-standalone-preset.js" charset="UTF-8"> </script> <script> window.onload = function() { // Begin Swagger UI call region const ui = SwaggerUIBundle({ url: "https://petstore.swagger.io/v2/swagger.json", dom_id: '#swagger-ui', deepLinking: true, presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], plugins: [ SwaggerUIBundle.plugins.DownloadUrl ], layout: "StandaloneLayout" }) // End Swagger UI call region window.ui = ui } </script> </body> </html>docker-compose.ymlを修正し、作成した
index.html
をnginxのデフォルトの公開ディレクトリである/usr/share/nginx/html
配下に配置します。docker-compose.ymlservices:![スクリーンショット 2020-10-25 18.23.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140792/22cf7096-e237-7c2e-3aa5-0959d4776657.png) api: (略) db: (略) nginx: image: nginx:1.19.3 ports: - '80:80' command: [nginx-debug, '-g', 'daemon off;'] volumes: - ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf + - ./nginx/html/swagger-ui:/usr/share/nginx/html/swagger-ui depends_on: - api volumes: mysql_data:
/swagger-ui
にアクセスしたらindex.html
が表示されるようnginxの設定を追記します。default.confserver { listen 80; server_name localhost; location / { proxy_set_header Host localhost; proxy_pass http://api:3000; } # "swagger-ui"にアクセスがあったときの処理 location /swagger-ui { alias /usr/share/nginx/html/swagger-ui; } }コンテナ起動後、
localhost:80/swagger-ui
にアクセスして以下のような画面が表示されればOKです。ローカルのSwagger SpecがSwagger UI上に反映されるようにする
Swagger UIの
url
を変更することで参照するSwagger Specを変更できます。ローカルのSwagger Specを参照するように変更します。
index.html- url: "https://petstore.swagger.io/v2/swagger.json", + url: "./api.yml",上記の変更でローカル環境に配置された
./nginx/html/swagger-ui/api.yml
の内容がSwagger UIへ反映されます。なお、
./nginx/html/swagger-ui/
ディレクトリはバインドマウントされているので、ローカルでSwagger Spec編集後、リロードすればコンテナのSwagger UIに変更内容が反映されます。サンプルとして作成した
GET /events
を実行するSwagger Specは以下の通りです。api.ymlopenapi: 3.0.2 info: title: サンプルAPI version: 1.0.0 servers: - url: http://localhost:3000 tags: - name: イベント paths: /events: get: tags: - イベント description: イベント一覧取得 responses: 200: description: 成功 content: application/json: schema: type: array description: イベントの配列 items: $ref: "#/components/schemas/Event" components: schemas: Event: type: object properties: id: description: ID type: integer format: int64 example: 1 title: description: タイトル type: string example: サンプルイベント created_at: description: 作成日 type: string format: date-time example: 2020-04-01 10:00 updated_at: description: 更新日 type: string format: date-time example: 2020-04-01 10:00コンテナ起動後、以下のような画面が表示されればOKです。
CORSの設定をする
Swagger UIは
localhost:80
、APIはlocalhost:3000
で起動しています。
この状態でSwagger UIからAPIにリクエストを送るとオリジンをまたがっているためAccess to fetch at 'http://localhost:3000/events' from origin 'http://localhost' has been blocked by CORS policy
というエラーが発生します。CORSの設定を行い、Swagger UIからAPIリクエストが送れるようにします。
今回はrack-corsを利用してCORSの設定を行ます。Gemfilegem 'rack-cors'config/initializers/cors.rbRails.application.config.middleware.insert_before 0, Rack::Cors do unless Rails.env.production? allow do origins(['localhost', /localhost:\d+\Z/]) resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end endコンテナ起動後、リクエストが正常に返ってくればOKです。
参考: CRUD操作を行うSwagger Spec
- GET /events
- POST /events
- GET /events/{id}
- PATCH /events/{id}
- DELETE /events/{id}
上記のエンドポイントに関するSwagger Specは以下の通りです。
api.ymlopenapi: 3.0.2 info: title: サンプルAPI version: 1.0.0 servers: - url: http://localhost:3000 tags: - name: イベント paths: /events: get: tags: - イベント description: イベント一覧取得 responses: 200: description: 成功 content: application/json: schema: type: array description: イベントの配列 items: $ref: "#/components/schemas/Event" post: tags: - イベント description: イベント登録 requestBody: content: application/json: schema: type: object properties: title: type: string example: サンプルイベント responses: 201: description: 作成 /events/{event_id}: get: tags: - イベント description: イベント詳細 parameters: - name: event_id in: path description: イベントID required: true schema: type: integer format: int64 example: 1 responses: 200: description: 成功 content: application/json: schema: type: object $ref: "#/components/schemas/Event" 404: description: event not found patch: tags: - イベント description: イベント更新 parameters: - name: event_id in: path description: id required: true schema: type: integer format: int64 example: 1 requestBody: content: application/json: schema: type: object properties: title: type: string example: サンプルイベント responses: 200: description: 成功 content: application/json: schema: type: object properties: activity: $ref: "#/components/schemas/Event" delete: tags: - イベント description: イベント削除 parameters: - name: event_id in: path description: id required: true schema: type: integer format: int64 example: 1 responses: 204: description: No Content components: schemas: Event: type: object properties: id: description: ID type: integer format: int64 example: 1 title: description: タイトル type: string example: サンプルイベント created_at: description: 作成日 type: string format: date-time example: 2020-04-01 10:00 updated_at: description: 更新日 type: string format: date-time example: 2020-04-01 10:00画面は以下のようになります。
まとめ
以上でAPIとSwagger UIを統合した開発環境の構築手順の紹介を終わります。
- nginxを利用することでSwagger UIとAPIを組み合わせる
- nginxのリバースプロキシ設定は『/etc/nginx/conf.d』配下に作成
- オリジンをまたがるリクエストをする際はCORSの設定が必要になる
- 自作のSwagger SpecをSwagger UIに反映させるにはindex.htmlのurlを変更する
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!
- 投稿日:2020-10-25T20:06:01+09:00
【3分でわかる】Rubyの継承とは?わかりやすく要点のみ解説!
はじめに
Rubyを学び始めると「継承」についての理解が必要になります。難しく考えなくても、なーんだそんなことか。となるように3分でまとめます。
結論:重複しているところをまとめてくっつけるだけ
下記の中で、[attr_accessor :a]と[def aa]が被っていて不効率だなと思う時ありますよね。そういう時に、被っている[attr_accessor :a]と[def aa]をまとめたclass Cを作ります。
class A attr_accessor :a :b :c def aa end def aaa end end class B attr_accessor :c :d :e def aa end def bbb end endクラスCを作ってAとBに継承させる
class C #何度も書いていた2つを新たなクラスCに記載 attr_accessor :c def aa end end class A < C #<Cと記載することで最初と同じ機能 attr_accessor :b :c def aaa end end class B < C #<Cと記載することで最初と同じ機能 attr_accessor :d :e def bbb end end今回、新しく作ったclass Cは親クラス、継承するclass Aとclass Bはサブクラスと呼ばれるようなので覚えておきましょう。
- 投稿日:2020-10-25T17:35:31+09:00
【並び替え】あるユーザーのフォロー、フォロワーをフォローした、フォローされた順(降順)に並び替える!
概要
あるユーザーのフォロー、フォロワーをフォローした、フォローされた順(降順)に並び替えた時のことを備忘録として記録します。
環境
・ruby '2.5.7'
・rails '5.2.3'前提
・ユーザーのフォロー機能は実装済であること
【参考】
第14章 ユーザーをフォローする - Railsチュートリアル過程
1.実装することの確認
「あるユーザー(
@user
)のフォロー、フォロワーを(@users
)フォローした、フォローされた順(降順)に並び替える(order("relationships.created_at DESC")
)」これを具体的にコードにしていきます!
2.following,followersアクションを定義する
users_controllerにfollowing,followersアクションを定義していきます。
controllers/users_controller.rbclass UsersController < ApplicationController (省略) def following @title = "フォロー" @user = User.find(params[:id]) get_follower_user_ids = Relationship.where(follower_id: @user.id).pluck(:followed_id) @users = User.includes(:passive_relationships).where(id: get_follower_user_ids).order("relationships.created_at DESC").paginate(page: params[:page]) render 'show_follow' end def followers @title = "フォロワー" @user = User.find(params[:id]) get_followed_user_ids = Relationship.where(followed_id: @user.id).pluck(:follower_id) @users = User.includes(:active_relationships).where(id: get_followed_user_ids).order("relationships.created_at DESC").paginate(page: params[:page]) render 'show_follow' end (省略) endコードを順番に説明していきます!(followingアクションのみ説明します)
①
@user = User.find(params[:id])
で表示されているユーザーを@user
に代入します。②
get_follower_user_ids = Relationship.where(follower_id: @user.id).pluck(:followed_id)
で@user
にフォローされているユーザーのidをget_follower_user_ids
に代入します。③
@users = User.includes(:passive_relationships).where(id: get_follower_user_ids).order("relationships.created_at DESC").paginate(page: params[:page])
で@user
にフォローされているユーザーを@users
に代入します。ここで、
includes(:passive_relationships)
とすることで、Relationshipモデルを参照できるようになり、order("relationships.created_at DESC")
で並び替えることができます。結果
これで、あるユーザーのフォロー、フォロワーをフォローした、フォローされた順(降順)に並び替えることができました!
- 投稿日:2020-10-25T17:34:59+09:00
配列の中に配列がある場合の値の出力の仕方
普通の配列
colors =["white", "black", "red", "green", "blue"]colorsという配列の中に、5つの要素が入っています。
先頭から順番に0、1、2、3…とインデックス番号が割り当てられています。
※1、2、3…ではないので注意!出力する際は、
puts colors[2]とすると、3番目のredが取り出せます。
配列の中に配列
animals = [["tuna", "octopus", "shark"], ["dog", "cat", "pig"], ["crow", "swan", "eagle"]]こんな感じのやつです。
animalsという配列の中に、海の動物、陸の動物、空の動物をそれぞれ3種類ずつ入れた配列が入っています。中身を全てまとめて出力する
puts animals↓
<出力結果> tuna octopus shark dog cat pig crow swan eagle海の動物だけを出力する
海の動物は0というインデックス番号が割り当てられています。
puts animals[0]↓
<出力結果> tuna octopus sharkタコだけを出力する
インデックス番号0という海の動物の中で、
さらにインデックス番号1が割り当てられています。puts animals[0][1]↓
<出力結果> octopus指定して全て出力したもの
=> は出力されたものを表しています。
puts animals[0][0] => tuna puts animals[0][1] => octopus puts animals[0][2] => shark puts animals[1][0] => dog puts animals[1][1] => cat puts animals[1][2] => pig puts animals[2][0] => crow puts animals[2][1] => swan puts animals[2][2] => eagleさらに複雑な配列
構造が複雑になっても、同じような感じで取り出すことができます。
countries = [["Japan", "America"], [["Brazil", "Russia"],["China", "India"]]]countriesという配列の中に2つの配列があり、
2つ目の配列の中にはさらにもう1つの配列が入っています。puts countries[0]↓
<出力結果> Japan Americaputs countries[1]↓
<出力結果> Brazil Russia China Indiaputs countries[1][0]↓
<出力結果> Brazil Russia指定して全て出力したもの
puts countries[0][0] => Japan puts countries[0][1] => America puts countries[1][0][0] => Brazil puts countries[1][0][1] => Russia puts countries[1][1][0] => China puts countries[1][1][1] => Indiaここからさらに四重とかになったとしても、同様の記述で値を取り出せます。
- 投稿日:2020-10-25T17:30:25+09:00
【Rails】パフォーマンス改善にすぐに役立つTips集
はじめに
Railsにおけるパフォーマンス改善に役立つTipsを集めてみました。
すぐに使えるものから、少し改善に時間がかかるものまで幅広く集めています。開発中のアプリのパフォーマンス改善のお役に立てれば幸いです。
【Tips1】 N+1を改善する
何はともあれN+1が発生していたら、それを解消するようにしましょう。
大抵の場合、そこがアプリのパフォーマンスのボトルネックになっているはずです。books_controller.rbclass BooksController < ApplicationController def index @book = Book.all end endindex.html.erb<% @book.each do |book| <%= book.title %> <%= book.user.name %> # ここでN+1が起きている <% end %>上のコード例だとbookの関連先のuserを読み込むところでN+1が起きています。
下記のようにコントローラーを書き換えましょう。
books_controller.rbclass BooksController < ApplicationController def index @book = Book.includes(:user) end end
モデル.all
で全てのモデルを取得してきている場所はN+1が起きている可能性が高くなり、大抵allは使わなくなることが多いです。
N+1を検知するためにgemのbullet
をアプリへ導入するのもおすすめです。https://github.com/flyerhzm/bullet
また、上の例だと
includes
を使用していますが、preload
とeager_load
を使い分けられるようになるといいですね。ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い
【Tips2】 countではなくてsizeを使用する
countを使用するとSQLを発行してしまいます。
そのため、モデルの数などを調べたい時はsize
などで代用しましょう。意外とやってしまいがちなケースですが、
count
をsize
へ置き換えるだけなので、手軽にできます。countを使用した場合
user = User.all user.count # count関数を用いたSELECT文が発行されてしまう (4.6ms) SELECT COUNT(*) FROM `users` => 100sizeを使用した場合
user = User.all user.size # SQLクエリは発行されない => 100【Tips3】exist?の使用を控える
count
と同じくexist?
はモデルオブジェクトに使用すると、sqlを発行してしまいます。
存在するかを確認したい場合などはpresent?
などで代用しましょう。exsit?を使用した場合
user = User.where(deleted: true) user.exist? # SQLが発行される User Load (5.4ms) SELECT `users`.* FROM `users` WHERE `users`.`deleted` = TRUE => []present?を使用した場合
user = User.where(deleted: true) user.present? # SQLは発行されない => []【Tips4】allで取得した結果をeachで回さない
全ユーザーを
all
で取得してそれをeachで回す・・・のようなパターンです。
バッチ処理とかにありがちなパターンですね。User.all.each do |user| # 何かuserのオブジェクトを使用して処理をするコード endN+1のTipsでも触れましたが、にallが処理に入ってきたときは一度そのコードを疑ってかかりましょう。
上述の実装だとUserの全件をメモリに展開してから、ひとつひとつの処理をeachで行っていくため、メモリ消費が激しくなります。
allのかわりにfind_eachを使いましょう。
User.find_each do |user| # 何かuserのオブジェクトを使用して処理をするコード endfind_eachはレコードを1000件取得ずつ取得し、その取得したレコードを1件ずつ処理してきてくれます。
1000件取得し終わったらまた、次の1000件を取得してきて・・・の繰り返しとなります。
ちなみにレコード展開数を指定したかったらその姉妹メソッドであるfind_in_batchesを使用しましょう。メソッドの引数で、レコード数を指定できます。
【Tips5】不必要なActive Recordオブジェクトの生成
上述のN+1問題や、eachパターンほどではないですが、これも気をつけないとやってしまいがちな実装となります。
user_names = User.all.map(&:name)上述のmapを使用したやり方は不必要なActive Recordオブジェクトを生成してしまい、パフォーマンスがあまりよくありません。
Active Recordオブジェクトは膨大な数のモジュールやメソッドをラップしているので、生成コストが高く、それだけでメモリを費やしてしまいます。
Active Recordオブジェクトを生成しなくてもよいやり方があるならば、そちらのやり方を考えましょう。
user_names = User.pluck(:name)pluckを使用することで、不要なオブジェクト生成を回避できました。
【Tips6】 キャッシュや非同期処理の導入を検討する
パフォーマンス悪化箇所にはキャッシュを使ったり、処理を非同期にする方法もあります。
キャッシュを使う
Redisなどの導入を検討する。マスター系のデータ読み込みなどに検討してみるといいかも。
ただ、導入箇所をよく検討しないとバグの原因になりがちです。処理を非同期にする
gemのsidekiq
やdelayed job
などで重い処理を非同期にしてしまう。
メール送信処理を非同期にする方法をよく見かけます。終わりに
いかがだったでしょうか。
以上、Railsでコーディングする上で意識するだけで簡単にパフォーマンス改善ができるTipsを紹介しました。他にもこんなやり方あるよだったり、ここが間違っているよなどありましたらコメント欄でそっとお知らせください笑
- 投稿日:2020-10-25T16:41:54+09:00
【Ruby on Rails】URLのidをカラム名に変更
はじめに
Railsでは、routes.rbでresourcesメソッドを使用すると、通常:idパラメータがデフォルトで使われますが、
場合によっては表示を変更したい場合もあるかと思います。
今回はidをnameカラムに変更する方法を記載します。目標
開発環境
ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina前提
※ ▶◯◯ を選択すると、説明等が出てきますので、
よくわからない場合の参考にしていただければと思います。【Ruby on Rails】gemのdeviseを使用し、名前とパスワードのみでログインする方法
こちらをベースにURLを変更していきます。準備
前提記事では、URLがmypageになっているため、
今回はあえてidをつけたURLにしていきます。
※変更点のみ記述します。config/routes.rbget 'mypage', to: 'homes#mypage' ↓ get 'mypage/:id', to: 'homes#mypage', as: 'mypage'app/controllers/users/registrations_controller.rbdef after_sign_up_path_for(resource) mypage_path end ↓ def after_sign_up_path_for(resource) mypage_path(@user) endapp/controllers/users/sessions_controller.rbdef after_sign_in_path_for(resource) mypage_path end ↓ def after_sign_in_path_for(resource) mypage_path(@user) endapp/controllers/homes_controller.rbdef mypage end ↓ def mypage @user = User.find(params[:id]) endこれでidがついたURLができました。
idをnameカラムに変更
同一の名前のURLをなくす
URLをカラムにするためには、同一のnameをなくす必要がありまあす。
そこでvalidationをかけます。モデルのバリデーション
app/models/user.rbvalidates :name, uniqueness: trueDBのバリデーション
ターミナル$ rails g migration add_index_users_namedb/migrate/xxxxxxxxxxxxx_add_index_users_name.rbclass AddIndexUsersName < ActiveRecord::Migration def change add_index :users, :name, unique: true end endターミナル$ rails db:migrate実際にidをnameに変更
findをfind_byにするのが肝です。
config/routes.rbget 'mypage/:id', to: 'homes#mypage', as: 'mypage' ↓ get 'mypage/:name', to: 'homes#mypage', as: 'mypage'app/controllers/homes_controller.rbdef mypage @user = User.find(params[:id]) end ↓ def mypage @user = User.find_by(name: params[:id]) end加えてリンク先もnameで指定するために
to_paramメソッドを使用します、app/models/user.rbvalidates :name, uniqueness: true # ↓追加 def to_param name endこれで目標と同じになるはずです。
resourcesを使用する場合
resourcesに下記のように追記すればOKです。
resources :users, param: :name
これでリンク先もnameで表示されるはずです。
まとめ
このように数字ではなく、文字列にすることで、
どのページにいるかがわかりやすく、利便性が高まります。
また、SNSのようなサービスで数字のIDを使用してしまうと、
今何人の登録者がいるかわかってしまうため、あまり推奨できる方法ではないです。
その時はこの方法を試してみてください。またtwitterではQiitaにはアップしていない技術や考え方もアップしていますので、
よければフォローして頂けると嬉しいです。
詳しくはこちら https://twitter.com/japwork
- 投稿日:2020-10-25T16:27:20+09:00
Rubyでheavy(蛇)!?
0.恐竜×スキルチェック
ここのところ激務につき、ほとんどスキルアップできていなかった。
念願のテレワークになっているのに何故か、テレワーク前より忙しくなっている。
そして何より、恐竜ちゃんを出すプログラムのアイデアがなくなりつつある。
さらに最近知ったのだが、「恐竜は爬虫類である」
そこでスキルチェックで見つけてしまったのが「へび」。
つけたタイトルも何気なく気に入っているし運命を感じたので、
解くためのプロセスを書いてみた。そしてランクが上がるほど、問題文が長くなり、『理解度とその世界観についていけるか』の勝負になる。
『問題を解けることが目的』なのであって、
コードの美しさ、短さを求める方には、ここから先の文章は不要です。最後に汚いコードを載せてみる。
そして、『どこか懐かしさと寒さを感じさせるタイトルはパリピには受けない事』
を反省しつつ始めてみる。1.問題「へび」
問題を画像で貼り付けてみる。
2.移動先の境界チェック
2.1移動範囲
一行目に表示される情報として、ヘビの移動できる縦幅と横幅がある。
この座標縦をy、横をxとすると
0<=y<=H-1,0<=x<=W-1が移動可能範囲となる。2.2移動先に壁がないか
壁をあらわすのは#で、.の場合は移動可能。
ここでわかるのは、2.1を満たさない場合がNG,
2.2を満たさない場合もNG,両方満たす場合がOKとなる。※余談だが、インドミナスレックスなどの恐竜様は壁ごとぶち破るので
このルールは通用しない。(俺様がルールだという奴でしょう。)上記を判定する関数として作成したのがcheck_limit関数
3.方向と遷移する座標
ここでは、方角(向いている方角)と進む向きが与えられた時に、
遷移先の座標が現在の座標と比較して、行方向と列方向にどのくらい
移動するか、次の方角を確認する。図で表すと下記の関係性になる
3.1向いている方角が北(N)の時
右向きに進む時、今いる場所から右に、つまり0行1列移動する。
次の方角は、東になる。
左向きに進む場合、今いる場所から左に移動、つまり0行−1列移動する。
次の方角は西になる。
向きが指定されていない場合(方向転換する時刻でない場合がここにあたり、noneとする)、今いる場所から上に移動、つまり−1行0列移動する。
次の方角は、北になる。3.2向いている方角が東(E)の時
右向きに進む時、今いる場所から1行(下)0列移動する
次の方角は、南になる。
左向きに進む場合、今いる場所から−1行(上)0列移動する。
次の方角は、北になる。
向きがnoneの時は、今いる場所から0行1列(右)移動する
次の方角は東になる。この情報を元に作成したのがcheck_direction関数
(checkというよりgetがいい事に今気がつく。)4.座標重複チェック
一度通った座標は既に蛇の胴体があるので進めない。
との事。どうやらアナコンダのような巨大な蛇を想定しているらしい。
遷移先が既に通った座標の場合、移動がストップ。5.コード例
全体的な流れ
(1)入力値を取得
(2)方向転換する時刻配列(tarr配列)とそれに対応する方向配列(lrarr)
や座標データ取得(arr2配列)を整理する処理
(3)座標の行き止まり判定や、移動座標や移動方角取得関数の作成
(4)時刻0から99までの間、以下を繰り返す
(ⅰ)現在の座標(sy,sx)と向いてる方角と進む方向に対し、遷移先座標(ny,nx)と次の方角(next_dir)を求める
(ⅱ)遷移先座標が行き止まりかチェック(行き止まりの場合ループを抜ける)
(ⅲ)行き止まりでない場合、今まで通った座標でないかチェックする
(ⅳ)今まで通った座標でない場合、この座標を(sy,sx)とし、snake_arrに格納する。
(ⅴ)今まで通った座標の場合(snake_arrに登録されている座標の場合)ループを抜ける
(5)snake_arr配列に登録された座標のarr2データに*を代入する
(6)arr2を出力
# 自分の得意な言語で # Let's チャレンジ!! in1 = gets.chomp! #puts(in1) arr1=in1.split(' ') #print(arr1) row1=arr1[0].to_i col1=arr1[1].to_i sy=arr1[2].to_i sx=arr1[3].to_i num1=arr1[4].to_i arr2 = Array.new(row1) { Array.new(col1,"") } #print(arr2) for i in 1..row1 do tmp1=gets.chomp.to_s for j in 1..col1 do arr2[i-1][j-1]=tmp1[j-1...j] end end tarr=[] lrarr=[] for i in 1..num1 do tmp1=gets.chomp.to_s arr3=tmp1.split(" ") tarr.push(arr3[0].to_i) lrarr.push(arr3[1]) #in3.push(tmp1) end def check_limit(arr0,i,j,row1,col1) if i<0 or i>row1-1 flg=1 elsif j<0 or j>col1-1 flg=1 elsif arr0[i][j]=="#" flg=1 else flg=0 end return flg end def check_direction(p_dir,rlstr) retstr="" if p_dir=="N" if rlstr=="R" retstr="0,1,E" elsif rlstr=="L" retstr="0,-1,W" else retstr="-1,0,N" end elsif p_dir=="S" if rlstr=="R" retstr="0,-1,W" elsif rlstr=="L" retstr="0,1,E" else retstr="1,0,S" end elsif p_dir=="E" if rlstr=="R" retstr="1,0,S" elsif rlstr=="L" retstr="-1,0,N" else retstr="0,1,E" end else if rlstr=="R" retstr="-1,0,N" elsif rlstr=="L" retstr="1,0,S" else retstr="0,-1,W" end end return retstr end def find_lr(sarr,retarr,i2,num1) rnum=-1 for i in 0..num1-1 do if sarr[i]==i2 rnum=i break end end return retarr[rnum] end snake_arr=[] snk_str=arr1[2]+" "+arr1[3] snake_arr.push(snk_str) #print(snake_arr) p_dir='N' flg_e=0 for i in 0..99 do if tarr.count(i)==0 retstr=check_direction(p_dir,"none") tmparr=retstr.split(",") ny=tmparr[0].to_i+sy nx=tmparr[1].to_i+sx next_dir=tmparr[2] flg=check_limit(arr2,ny,nx,row1,col1) else lrstr=find_lr(tarr,lrarr,i,num1) retstr=check_direction(p_dir,lrstr) tmparr=retstr.split(",") ny=tmparr[0].to_i+sy nx=tmparr[1].to_i+sx next_dir=tmparr[2] flg=check_limit(arr2,ny,nx,row1,col1) end if flg==0 sy=ny sx=nx p_dir=next_dir str1=sy.to_s+" "+sx.to_s if snake_arr.count(str1)==0 snake_arr.push(str1) else flg_e=1 #puts(1) end else flg_e=1 #puts(2) end if flg_e==1 break end end #print(snake_arr) #print(snake_arr.length) for i in 0..snake_arr.length-1 do tmp1=snake_arr[i].to_s.split(" ") arr2[tmp1[0].to_i][tmp1[1].to_i]="*" end str1="" for i in 0..row1-1 do str1="" for j in 0..col1-1 do str1=str1+arr2[i][j] end puts(str1) end
- 投稿日:2020-10-25T16:03:30+09:00
[Rails]Google Maps APIによるGoogle Mapの表示と複数地点間のルート検索
はじめに
参考になる記事がとても少なく、ポートフォリオ作成で一番苦戦した部分なので、自分の学習のためアウトプットとして残す、とともにだれかの役に立てればいいなと思ったので書きました!
初学者なりにこの記述の意味はなんだ?と思う部分はしっかり説明したつもりです。
当たり前だろ!と思う部分も多々あると思いますがご了承ください。目標
Google Maps APIでGoogle Mapを表示させるとともに、マーカーの吹き出しから任意にルート検索リストに追加でき、複数地点のルートを検索する機能を目標とします。
開発環境
・Ruby: 2.5.1
・Rails: 5.2.1
・OS: macOS前提
・Slimの導入
・公式のGoogle Maps Platformで以下のAPIの有効化
・Maps JavaScript API
→ GoogleMapの表示
・Geocoding API
→ 住所から緯度経度の算出
・Directions API
→ ルート検索設定
1. 必要なgemをインストール
Gemfilegem 'dotenv-rails' # APIキーを環境変数化 gem 'gon' # コントローラーで定義したインスタンス変数をJavaScript内で使用出来るようにする。 gem 'geocoder' # 住所から緯度経度を算出する。ターミナル$ bundle install2. APIキーを環境変数化
アプリケーション直下に「.env」ファイルを作成
ターミナル$ touch .env自身のAPIキーを' 'の中に記述
.envGOOGLE_MAP_API = '自身のコピーしたAPIキー'.gitignore/.env3. turbolinksの無効化
Gemfilegem 'turbolinks' # この行を削除app/assets/javascripts/application.js//= require turbolinks // この行を削除
data-turbolinks-track':'reload'属性の削除
app/views/layouts/application.html.slim= stylesheet_link_tag 'application', media: 'all' = javascript_include_tag 'application'4. Geocoding APIを使用できるようにする
geocorderの設定ファイルを作成し、編集
ターミナル$ touch config/initializers/geocoder.rbconfig/initializers/geocoder.rb# 追記 Geocoder.configure( lookup: :google, api_key: ENV['GOOGLE_MAP_API'] )これで設定は終了です。ここからGoogleMapを表示していく実装に入ります。
GoogleMapの表示
1. 追加したいモデルにカラムを追加
自分のアプリの場合はPlaceモデルにaddressカラムを追加します。
latitude, longitudeカラムはGeocoding APIによってaddressカラムの値から算出された経度・緯度の値です。小数の値なので型はfloatを使います。ターミナル$ rails g migration AddColumnsToPlaces address:string latitude:float longitude:floatターミナル$ rails db:migrate2. モデルを編集
models/place.rb# 追記 geocoded_by :address # addressカラムを基準に緯度経度を算出する。 after_validation :geocode # 住所変更時に緯度経度も変更する。3. コントローラーを編集
controllers/places_controller.rbdef index @place = Place.all gon.place = @place # 追記 end private def place_params # ストロングパラメーターに「address」を追加 params.require(:place).permit(:name, :description, :image, :address) end4. ビューを編集
①application.html.slimを編集
CSSとJavaScriptより先に、gonを読み込むよう記述します。views/layouts/application.html.slimdoctype html html head title | app_name = csrf_meta_tags = csp_meta_tag = include_gon # 追記 = stylesheet_link_tag 'application', media: 'all' = javascript_include_tag 'application'②新規登録画面に住所入力フォームを追加
views/places/new.html.slim= f.label :address, '住所' = f.text_field :address, class: 'form-control'③GoogleMapを表示するファイルに記述
views/places/index.html.slimdiv id = 'map_index' # idを付与, この部分にjsファイルで記述したGoogle Mapが埋め込まれる - google_api = "https://maps.googleapis.com/maps/api/js?key=#{ ENV['GOOGLE_MAP_API'] }&callback=initMap".html_safe script{ async src = google_api } .map-route < ルート検索リスト > ul id = "route-list" class = "list-group" # jsファイルで吹き出しの追加ボタンによってその場所がli要素に追加される div id = 'directions-panel' # 距離・時間が埋め込まれる < 各地点間の距離・時間 > ul id = "display-list" class = "display-group" .map-search = button_tag "ルート検索", id: "btn-search", class: "btn btn-primary", onclick: "search()" # クリック処理でsearch()関数を呼び出す[ google_api = 〜〜〜〜の部分について ]
→ callback処理で読み込み時にinitMap関数を呼び出す。
→ .html_safeはエスケープ処理
→ async属性によって非同期でJavaScriptを読み込みレンダリングを早くする。④GoogleMapで表示したいサイズをscssに記述
stylesheets/application.scss#map_index{ height: 400px; width: 400px; }5. JavaScriptのファイルを編集
ここが肝です。
assets/javascripts直下に新たなファイルを作成し、記述します。
だいぶ長く見にくいかと思いますが、変数を定義したのち、関数の定義をそれぞれ行っているだけです。
関数は、
・initMap
・markerEvent( i )
・addPlace(name, lat, lng, number)
・search()
の順で4つがあります。
わかりにくい部分やポイントは、コメントアウトで説明していますので参考にしてください。assets/javascripts/googlemap.jsvar map var geocoder var marker = []; var infoWindow = []; var markerData = gon.places; // コントローラーで定義したインスタンス変数を変数に代入 var place_name = []; var place_lat = []; var place_lng = []; // GoogleMapを表示する関数(callback処理で呼び出される) function initMap(){ geocoder = new google.maps.Geocoder() // ビューのid='map_index'の部分にGoogleMapを埋め込む map = new google.maps.Map(document.getElementById('map_index'), { center: { lat: 35.6585, lng: 139.7486 }, // 東京タワーを中心 zoom: 9, }); // 繰り返し処理でマーカーと吹き出しを複数表示させる for (var i = 0; i < markerData.length; i++) { // 各地点の緯度経度を算出 markerLatLng = new google.maps.LatLng({ lat: markerData[i]['latitude'], lng: markerData[i]['longitude'] }); // マーカーの表示 marker[i] = new google.maps.Marker({ position: markerLatLng, map: map }); // 吹き出しの表示 let id = markerData[i]['id'] place_name[i]= markerData[i]['name']; place_lat[i]= markerData[i]['latitude']; place_lng[i]= markerData[i]['longitude']; infoWindow[i] = new google.maps.InfoWindow({ // 吹き出しの中身, 引数で各属性の配列と配列番号を渡す content: `<a href='/places/${ id }'>${ markerData[i]['name'] }</a><input type="button" value="追加" onclick="addPlace(place_name, place_lat, place_lng, ${i})">` }); markerEvent(i); } } } // マーカーをクリックしたら吹き出しを表示 function markerEvent(i) { marker[i].addListener('click', function () { infoWindow[i].open(map, marker[i]); }); } // リストに追加する function addPlace(name, lat, lng, number){ var li = $('<li>', { text: name[number], "class": "list-group-item" }); li.attr("data-lat", lat[number]); // data-latという属性にlat[number]を入れる li.attr("data-lng", lng[number]); // data-lngという属性にlng[number]を入れる $('#route-list').append(li); // idがroute-listの要素の一番後ろにliを追加 } // ルートを検索する function search() { var points = $('#route-list li'); // 2地点以上のとき if (points.length >= 2){ var origin; // 開始地点 var destination; // 終了地点 var waypoints = []; // 経由地点 // origin, destination, waypointsを設定する for (var i = 0; i < points.length; i++) { points[i] = new google.maps.LatLng($(points[i]).attr("data-lat"), $(points[i]).attr("data-lng")); if (i == 0){ origin = points[i]; } else if (i == points.length-1){ destination = points[i]; } else { waypoints.push({ location: points[i], stopover: true }); } } // リクエストの作成 var request = { origin: origin, destination: destination, waypoints: waypoints, travelMode: google.maps.TravelMode.DRIVING }; // ルートサービスのリクエスト new google.maps.DirectionsService().route(request, function(response, status) { if (status == google.maps.DirectionsStatus.OK) { new google.maps.DirectionsRenderer({ map: map, suppressMarkers : true, polylineOptions: { // 描画される線についての設定 strokeColor: '#00ffdd', strokeOpacity: 1, strokeWeight: 5 } }).setDirections(response);//ライン描画部分 // 距離、時間を表示する var data = response.routes[0].legs; for (var i = 0; i < data.length; i++) { // 距離 var li = $('<li>', { text: data[i].distance.text, "class": "display-group-item" }); $('#display-list').append(li); // 時間 var li = $('<li>', { text: data[i].duration.text, "class": "display-group-item" }); $('#display-list').append(li); } const route = response.routes[0]; // ビューのid='directions-panel'の部分に埋め込む const summaryPanel = document.getElementById("directions-panel"); summaryPanel.innerHTML = ""; // 各地点間の距離・時間を表示 for (let i = 0; i < route.legs.length; i++) { const routeSegment = i + 1; summaryPanel.innerHTML += "<b>Route Segment: " + routeSegment + "</b><br>"; summaryPanel.innerHTML += route.legs[i].start_address + "<br>" + " ↓ " + "<br>"; summaryPanel.innerHTML += route.legs[i].end_address + "<br>"; summaryPanel.innerHTML += "<" + route.legs[i].distance.text + ","; summaryPanel.innerHTML += route.legs[i].duration.text + ">" + "<br>"; } } }); } }吹き出しの内容のcontent部分の補足:(データの受け渡しの方法で苦戦したので)
content: `<a href='/places/${ id }'>${ markerData[i]['name'] }</a><input type="button" value="追加" onclick="addPlace(place_name, place_lat, place_lng, ${i})">`addPlace(place_name, place_lat, place_lng, ${i})
この関数の呼び出しでは前の3つの引数は配列として渡しています。4つ目の引数は配列の中でどの情報かを表すための番号(インデックスと呼びます。)を式展開したものです。JavaScriptでの式展開はこの形だそうです。
このような引数を用意することで、関数addPlace(name, lat, lng, number)は正常にどのデータであるかという情報を処理できるのです。最後に
最後まで読んでくださり、ありがとうございます。
自分自身、現在ポートフォリオが完成に近づき就職活動を本格的に始め出したような状態です!
目標を持ってポートフォリオ作成、転職活動など行っている方を心から応援しています、共に頑張りましょう!!参考
- 投稿日:2020-10-25T14:59:24+09:00
Railsチュートリアル第4版:第2章 Toyアプリケーション
Railsチュートリアル第4版:第2章 Toyアプリケーション
2.1 アプリケーションの計画
Terminal-> % rails new toy_app create create README.md create Rakefile create .ruby-version create config.ru create .gitignore create Gemfile run git init from "." Initialized empty Git repository in /Users/**********/environment_2/toy_app/.git/ create package.json create app create app/assets/config/manifest.js create app/assets/stylesheets/application.css create app/channels/application_cable/channel.rb create app/channels/application_cable/connection.rb create app/controllers/application_controller.rb create app/helpers/application_helper.rb create app/javascript/channels/consumer.js create app/javascript/channels/index.js create app/javascript/packs/application.js create app/jobs/application_job.rb create app/mailers/application_mailer.rb create app/models/application_record.rb create app/views/layouts/application.html.erb create app/views/layouts/mailer.html.erb create app/views/layouts/mailer.text.erb create app/assets/images create app/assets/images/.keep create app/controllers/concerns/.keep create app/models/concerns/.keep create bin create bin/rails create bin/rake create bin/setup create bin/yarn create config create config/routes.rb create config/application.rb create config/environment.rb create config/cable.yml ・ ・ ・ ├─ retry@0.12.0 ├─ select-hose@2.0.0 ├─ selfsigned@1.10.8 ├─ serve-index@1.9.1 ├─ serve-static@1.14.1 ├─ sockjs-client@1.4.0 ├─ sockjs@0.3.20 ├─ spdy-transport@3.0.0 ├─ spdy@4.0.2 ├─ strip-eof@1.0.0 ├─ thunky@1.1.0 ├─ type-is@1.6.18 ├─ unpipe@1.0.0 ├─ utils-merge@1.0.1 ├─ wbuf@1.7.3 ├─ webpack-dev-middleware@3.7.2 ├─ webpack-dev-server@3.11.0 └─ ws@6.2.1 ✨ Done in 10.52s. Webpacker successfully installed ? ?Gemfilesource 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.7.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 6.0.3', '>= 6.0.3.4' # Use sqlite3 as the database for Active Record gem 'sqlite3', '~> 1.4' # Use Puma as the app server gem 'puma', '~> 4.1' # Use SCSS for stylesheets gem 'sass-rails', '>= 6' # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker gem 'webpacker', '~> 4.0' # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks gem 'turbolinks', '~> 5' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.7' # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 4.0' # Use Active Model has_secure_password # gem 'bcrypt', '~> 3.1.7' # Use Active Storage variant # gem 'image_processing', '~> 1.2' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.4.2', require: false group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] end group :development do # Access an interactive console on exception pages or by calling 'console' anywhere in the code. gem 'web-console', '>= 3.3.0' gem 'listen', '~> 3.2' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end group :test do # Adds support for Capybara system testing and selenium driver gem 'capybara', '>= 2.15' gem 'selenium-webdriver' # Easy installation and use of web drivers to run system tests with browsers gem 'webdrivers' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]Terminal-> % bundle install --without production [DEPRECATED] The `--without` flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use `bundle config set without 'production'`, and stop using this flag The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Using rake 13.0.1 Using concurrent-ruby 1.1.7 Using i18n 1.8.5 Using minitest 5.14.2 Using thread_safe 0.3.6 Using tzinfo 1.2.7 Using zeitwerk 2.4.0 Using activesupport 6.0.3.4 ・ ・ ・ Using webdrivers 4.4.1 Using webpacker 4.3.0 Bundle complete! 17 Gemfile dependencies, 74 gems now installed. Gems in the group production were not installed. Use `bundle info [gemname]` to see where a bundled gem is installed.Terminal-> % git init Reinitialized existing Git repository in /Users/**********/environment_2/toy_app/.git/ -> % git add -A -> % git commit -m "Initialize repository" [master (root-commit) 28e8b26] Initialize repository 93 files changed, 9253 insertions(+) create mode 100644 .browserslistrc create mode 100644 .generators create mode 100644 .gitignore create mode 100644 .ruby-version create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 README.md create mode 100644 Rakefile create mode 100644 app/assets/config/manifest.js create mode 100644 app/assets/images/.keep create mode 100644 app/assets/stylesheets/application.css create mode 100644 app/channels/application_cable/channel.rb create mode 100644 app/channels/application_cable/connection.rb create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/concerns/.keep create mode 100644 app/helpers/application_helper.rb create mode 100644 app/javascript/channels/consumer.js ・ ・ ・ create mode 100644 test/models/.keep create mode 100644 test/system/.keep create mode 100644 test/test_helper.rb create mode 100644 tmp/.keep create mode 100644 tmp/pids/.keep create mode 100644 vendor/.keep create mode 100644 yarn.lock
Terminal-> % git remote add origin https://github.com/**********/toy_app.git -> % git push origin master Enumerating objects: 105, done. Counting objects: 100% (105/105), done. Delta compression using up to 4 threads Compressing objects: 100% (87/87), done. Writing objects: 100% (105/105), 149.29 KiB | 4.82 MiB/s, done. Total 105 (delta 3), reused 0 (delta 0) remote: Resolving deltas: 100% (3/3), done. To https://github.com/**********/toy_app.git * [new branch] master -> masterapp/controllers/application_controller.rbclass ApplicationController < ActionController::Base def hello render html: "hello, world!" end endconfig/routes.rbRails.application.routes.draw do # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html root 'application#hello' endTerminal-> % heroku create Creating app... done, ⬢ dry-headland-50008 https://dry-headland-50008.herokuapp.com/ | https://git.heroku.com/dry-headland-50008.git -> % git push heroku master Enumerating objects: 112, done. Counting objects: 100% (112/112), done. Delta compression using up to 4 threads Compressing objects: 100% (94/94), done. Writing objects: 100% (112/112), 149.82 KiB | 3.12 MiB/s, done. Total 112 (delta 7), reused 0 (delta 0) remote: Compressing source files... done. ・ ・ ・ remote: An error occurred while installing sqlite3 (1.4.2), and Bundler cannot continue. remote: Make sure that `gem install sqlite3 -v '1.4.2' --source 'https://rubygems.org/'` remote: succeeds before bundling. remote: remote: In Gemfile: remote: sqlite3 remote: remote: ! remote: ! Failed to install gems via Bundler. remote: ! Detected sqlite3 gem which is not supported on Heroku: remote: ! https://devcenter.heroku.com/articles/sqlite3 remote: ! remote: ! Push rejected, failed to compile Ruby app. remote: remote: ! Push failed remote: Verifying deploy... remote: remote: ! Push rejected to dry-headland-50008. remote: To https://git.heroku.com/dry-headland-50008.git ! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to 'https://git.heroku.com/dry-headland-50008.git'2.1.1 ユーザーのモデル設計
2.1.2 マイクロポストのモデル設計
2.2 Usersリソース
Terminal-> % bin/rails g scaffold User name:string email:string Running via Spring preloader in process 17885 invoke active_record create db/migrate/20201023134008_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml invoke resource_route route resources :users invoke scaffold_controller create app/controllers/users_controller.rb invoke erb create app/views/users create app/views/users/index.html.erb create app/views/users/edit.html.erb create app/views/users/show.html.erb create app/views/users/new.html.erb create app/views/users/_form.html.erb invoke test_unit create test/controllers/users_controller_test.rb create test/system/users_test.rb invoke helper create app/helpers/users_helper.rb invoke test_unit invoke jbuilder create app/views/users/index.json.jbuilder create app/views/users/show.json.jbuilder create app/views/users/_user.json.jbuilder invoke assets invoke scss create app/assets/stylesheets/users.scss invoke scss create app/assets/stylesheets/scaffolds.scssTerminal-> % bin/rails db:migrate == 20201023134008 CreateUsers: migrating ====================================== -- create_table(:users) -> 0.0026s == 20201023134008 CreateUsers: migrated (0.0026s) =============================Terminal-> % bin/rails s => Booting Puma => Rails 6.0.3.4 application starting in development => Run `rails server --help` for more startup options2.2.1 ユーザーページを探検する
- 演習
- 1. CSSを知っている読者へ: 新しいユーザーを作成し、ブラウザのHTMLインスペクター機能を使って「User was successfully created.」の箇所を調べてみてください。ブラウザをリロードすると、その箇所はどうなるでしょうか?
- 2. emailを入力せず、名前だけを入力しようとした場合、どうなるでしょうか?
これは何を意図した問題だ?てっきり入力してくださいアラート出るのかの思った。
- 3. 「@example.com」のような間違ったメールアドレスを入力して更新しようとした場合、どうなるでしょうか?
- 4. 上記の演習で作成したユーザーを削除してみてください。ユーザーを削除したとき、Railsはどんなメッセージを表示するでしょうか? "Are you sure?"
2.2.2 MVCの挙動
config/routes.rbRails.application.routes.draw do resources :users root 'users#index' end
- 演習
- 1. 図 2.11を参考にしながら、/users/1/edit というURLにアクセスしたときの振る舞いについて図を書いてみてください。
省略- 2. 図示した振る舞いを見ながら、Scaffoldで生成されたコードの中でデータベースからユーザー情報を取得しているコードを探してみてください。
app/controllers/users_controller.rb
の@users = User.all
とか@user = User.find(params[:id])
かな、自信ないけど。- 3. ユーザーの情報を編集するページのファイル名は何でしょうか?
app/views/users/edit.html.erb
一旦コミットしたよ
Terminal-> % git add . -> % git commit -m "Usersリソース" ・ ・ ・ -> % git push origin master2.2.3 Usersリソースの欠点
2.3 Micropostsリソース
2.3.1 マイクロポストを探検する
Terminal-> % bin/rails g scaffold Micropost content:text user_id:integer Running via Spring preloader in process 19890 invoke active_record create db/migrate/20201024030447_create_microposts.rb create app/models/micropost.rb invoke test_unit create test/models/micropost_test.rb create test/fixtures/microposts.yml invoke resource_route route resources :microposts invoke scaffold_controller create app/controllers/microposts_controller.rb invoke erb create app/views/microposts create app/views/microposts/index.html.erb create app/views/microposts/edit.html.erb create app/views/microposts/show.html.erb create app/views/microposts/new.html.erb create app/views/microposts/_form.html.erb invoke test_unit create test/controllers/microposts_controller_test.rb create test/system/microposts_test.rb invoke helper create app/helpers/microposts_helper.rb invoke test_unit invoke jbuilder create app/views/microposts/index.json.jbuilder create app/views/microposts/show.json.jbuilder create app/views/microposts/_micropost.json.jbuilder invoke assets invoke scss create app/assets/stylesheets/microposts.scss invoke scss identical app/assets/stylesheets/scaffolds.scssTerminal-> % bin/rails db:migrate == 20201024030447 CreateMicroposts: migrating ================================= -- create_table(:microposts) -> 0.0032s == 20201024030447 CreateMicroposts: migrated (0.0033s) ========================
- 演習
- 1. CSSを知っている読者へ: 新しいマイクロポストを作成し、ブラウザのHTMLインスペクター機能を使って「Micropost was successfully created.」の箇所を調べてみてください。ブラウザをリロードすると、その箇所はどうなるでしょうか?
- 2. マイクロポストの作成画面で、ContentもUserも空にして作成しようとするどうなるでしょうか?
保存できちゃう。
- 3. 141文字以上の文字列をContentに入力した状態で、マイクロポストを作成しようとするとどうなるでしょうか? (ヒント: WikipediaのRubyの記事にある設計思想の引用文が140文字を超えているので、これをコピペしてみましょう)
- 4. 上記の演習で作成したマイクロポストを削除してみましょう。
一旦コミットしたよ
Terminal-> % git add . -> % git commit -m "Micropostsリソース" ・ ・ ・ -> % git push origin master2.3.2 マイクロポストをマイクロにする
app/models/micropost.rbclass Micropost < ApplicationRecord validates :content, length: { maximum: 140 } end
- 演習
- 1. 先ほど2.3.1.1の演習でやったように、もう一度Contentに141文字以上を入力してみましょう。どのように振る舞いが変わったでしょうか?
- 2. CSSを知っている読者へ: ブラウザのHTMLインスペクター機能を使って、表示されたエラーメッセージを調べてみてください。
一旦コミットしたよ
Terminal-> % git add . -> % git commit -m "文字数のvalidation追加" ・ ・ ・ -> % git push origin master2.3.3 ユーザーはたくさんマイクロポストを持っている
app/models/user.rbclass User < ApplicationRecord has_many :microposts # <= 追加 endapp/models/micropost.rbclass Micropost < ApplicationRecord belongs_to :user # <= 追加 validates :content, length: { maximum: 140 } end
- 演習
- 1. ユーザーのshowページを編集し、ユーザーの最初のマイクロポストを表示してみましょう。同ファイル内の他のコードから文法を推測してみてください (コラム 1.1で紹介した技術の出番です)。うまく表示できたかどうか、/users/1 にアクセスして確認してみましょう。
app/controllers/users_controller.rb# GET /users/1 # GET /users/1.json def show @first_micropost = @user.microposts.first # <= 追加 endapp/views/users/show.html.erb<p> <strong>Email:</strong> <%= @user.email %> </p> # 以下を追加 <p> <strong>Micropost:</strong> <%= @first_micropost.content %> </p> <%= link_to 'Edit', edit_user_path(@user) %> | <%= link_to 'Back', users_path %>
- 2. リスト 2.16は、マイクロポストのContentが存在しているかどうかを検証するバリデーションです。マイクロポストが空でないことを検証できているかどうか、実際に試してみましょう (図 2.16のようになっていると成功です)。
app/models/micropost.rbclass Micropost < ApplicationRecord belongs_to :user validates :content, length: { maximum: 140 }, presence: true end
- 3. リスト 2.17のFILL_INとなっている箇所を書き換えて、Userモデルのnameとemailが存在していることを検証してみてください (図 2.17)。
app/models/user.rbclass User < ApplicationRecord has_many :microposts validates :name, presence: true validates :email, presence: true end
2.3.4 継承の階層
- 演習
- 1. Applicationコントローラのファイルを開き、ApplicationControllerがActionController::Baseを継承している部分のコードを探してみてください。
app/controllers/application_controller.rbclass ApplicationController < ActionController::Base # <= ここのはず def hello render html: "hello, world!" end end
- 2. ApplicationRecordがActiveRecord::Baseを継承しているコードはどこにあるでしょうか? 先ほどの演習を参考に、探してみてください。ヒント: コントローラと本質的には同じ仕組みなので、app/modelsディレクトリ内にあるファイルを調べてみると...?)
app/models/application_record.rbclass ApplicationRecord < ActiveRecord::Base # <= ここのはず self.abstract_class = true end2.3.5 アプリケーションをデプロイする
ビューを一部修正。
コミットTerminal-> % git add . -> % git commit -m "first_micropost部分をコメントアウト" [master 59064e0] first_micropost部分をコメントアウト 1 file changed, 4 insertions(+), 4 deletions(-) -> % git push origin master Enumerating objects: 11, done. Counting objects: 100% (11/11), done. Delta compression using up to 4 threads Compressing objects: 100% (6/6), done. Writing objects: 100% (6/6), 531 bytes | 531.00 KiB/s, done. Total 6 (delta 5), reused 0 (delta 0) remote: Resolving deltas: 100% (5/5), completed with 5 local objects. To https://github.com/**********/toy_app.git 71fcd86..59064e0 master -> masterTerminal-> % git push heroku Enumerating objects: 215, done. Counting objects: 100% (215/215), done. Delta compression using up to 4 threads Compressing objects: 100% (192/192), done. Writing objects: 100% (215/215), 161.74 KiB | 3.94 MiB/s, done. Total 215 (delta 55), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: ・ ・ ・ remote: Gem files will remain installed in remote: /tmp/build_ef1fbbcc/vendor/bundle/ruby/2.7.0/gems/sqlite3-1.4.2 for inspection. remote: Results logged to remote: /tmp/build_ef1fbbcc/vendor/bundle/ruby/2.7.0/extensions/x86_64-linux/2.7.0/sqlite3-1.4.2/gem_make.out remote: remote: An error occurred while installing sqlite3 (1.4.2), and Bundler cannot continue. remote: Make sure that `gem install sqlite3 -v '1.4.2' --source 'https://rubygems.org/'` remote: succeeds before bundling. remote: remote: In Gemfile: remote: sqlite3 remote: remote: ! remote: ! Failed to install gems via Bundler. remote: ! Detected sqlite3 gem which is not supported on Heroku: remote: ! https://devcenter.heroku.com/articles/sqlite3 remote: ! remote: ! Push rejected, failed to compile Ruby app. remote: remote: ! Push failed remote: Verifying deploy... remote: remote: ! Push rejected to dry-headland-50008. remote: To https://git.heroku.com/dry-headland-50008.git ! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to 'https://git.heroku.com/dry-headland-50008.git'gemを修正しわすれていたので、
git push heroku
できなかったので修正。ここ修正忘れててつまずく人でてくるかも。(参考)Gemfilegroup :production do gem 'pg' endhttps://github.com/fukadashigeru/toy_app/commit/1d0d5c80762438ef72c2c507b240dda9819edf2a
Terminal-> % git push heroku Enumerating objects: 215, done. Counting objects: 100% (215/215), done. Delta compression using up to 4 threads Compressing objects: 100% (192/192), done. Writing objects: 100% (215/215), 161.75 KiB | 3.76 MiB/s, done. Total 215 (delta 55), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: ・ ・ ・ remote: ###### WARNING: remote: remote: There is a more recent Ruby version available for you to use: remote: remote: 2.7.2 remote: remote: The latest version will include security and bug fixes. We always recommend remote: running the latest version of your minor release. remote: remote: Please upgrade your Ruby version. remote: remote: For all available Ruby versions see: remote: https://devcenter.heroku.com/articles/ruby-support#supported-runtimes remote: remote: ###### WARNING: remote: remote: No Procfile detected, using the default web server. remote: We recommend explicitly declaring how to boot your server process via a Procfile. remote: https://devcenter.heroku.com/articles/ruby-default-web-server remote: remote: remote: -----> Discovering process types remote: Procfile declares types -> (none) remote: Default types for buildpack -> console, rake, web remote: remote: -----> Compressing... remote: Done: 77.8M remote: -----> Launching... remote: Released v6 remote: https://dry-headland-50008.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/dry-headland-50008.git * [new branch] master -> masterアクセスすると、なんかわからんけど落ちてるくさい。
Terminal-> % heroku run rails db:migrate Running rails db:migrate on ⬢ dry-headland-50008... up, run.3253 (Free) D, [2020-10-25T05:13:31.252614 #4] DEBUG -- : (13.5ms) CREATE TABLE "schema_migrations" ("version" character varying NOT NULL PRIMARY KEY) D, [2020-10-25T05:13:31.264484 #4] DEBUG -- : (8.8ms) CREATE TABLE "ar_internal_metadata" ("key" character varying NOT NULL PRIMARY KEY, "value" character varying, "created_at" timestamp(6) NOT NULL, "updated_at" timestamp(6) NOT NULL) D, [2020-10-25T05:13:31.302300 #4] DEBUG -- : (1.3ms) SELECT pg_try_advisory_lock(2791976884009269180) D, [2020-10-25T05:13:31.321970 #4] DEBUG -- : (2.6ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC I, [2020-10-25T05:13:31.323672 #4] INFO -- : Migrating to CreateUsers (20201023134008) == 20201023134008 CreateUsers: migrating ====================================== -- create_table(:users) D, [2020-10-25T05:13:31.328518 #4] DEBUG -- : (1.2ms) BEGIN D, [2020-10-25T05:13:31.337948 #4] DEBUG -- : (9.1ms) CREATE TABLE "users" ("id" bigserial primary key, "name" character varying, "email" character varying, "created_at" timestamp(6) NOT NULL, "updated_at" timestamp(6) NOT NULL) -> 0.0114s == 20201023134008 CreateUsers: migrated (0.0115s) ============================= D, [2020-10-25T05:13:31.348980 #4] DEBUG -- : primary::SchemaMigration Create (1.4ms) INSERT INTO "schema_migrations" ("version") VALUES ($1) RETURNING "version" [["version", "20201023134008"]] D, [2020-10-25T05:13:31.352253 #4] DEBUG -- : (2.9ms) COMMIT I, [2020-10-25T05:13:31.352397 #4] INFO -- : Migrating to CreateMicroposts (20201024030447) == 20201024030447 CreateMicroposts: migrating ================================= -- create_table(:microposts) D, [2020-10-25T05:13:31.356681 #4] DEBUG -- : (1.2ms) BEGIN D, [2020-10-25T05:13:31.365711 #4] DEBUG -- : (8.8ms) CREATE TABLE "microposts" ("id" bigserial primary key, "content" text, "user_id" integer, "created_at" timestamp(6) NOT NULL, "updated_at" timestamp(6) NOT NULL) -> 0.0123s == 20201024030447 CreateMicroposts: migrated (0.0124s) ======================== D, [2020-10-25T05:13:31.367853 #4] DEBUG -- : primary::SchemaMigration Create (1.2ms) INSERT INTO "schema_migrations" ("version") VALUES ($1) RETURNING "version" [["version", "20201024030447"]] D, [2020-10-25T05:13:31.370689 #4] DEBUG -- : (2.5ms) COMMIT D, [2020-10-25T05:13:31.381377 #4] DEBUG -- : ActiveRecord::InternalMetadata Load (1.3ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", "environment"], ["LIMIT", 1]] D, [2020-10-25T05:13:31.394744 #4] DEBUG -- : (1.1ms) BEGIN D, [2020-10-25T05:13:31.396634 #4] DEBUG -- : ActiveRecord::InternalMetadata Create (1.5ms) INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key" [["key", "environment"], ["value", "production"], ["created_at", "2020-10-25 05:13:31.392814"], ["updated_at", "2020-10-25 05:13:31.392814"]] D, [2020-10-25T05:13:31.399052 #4] DEBUG -- : (2.1ms) COMMIT D, [2020-10-25T05:13:31.400599 #4] DEBUG -- : (1.3ms) SELECT pg_advisory_unlock(2791976884009269180)https://dry-headland-50008.herokuapp.com/
- 演習
- 1. 本番環境で2〜3人のユーザーを作成してみましょう。
- 2. 本番環境で最初のユーザーのマイクロポストを作ってみましょう
https://dry-headland-50008.herokuapp.com/microposts
- 3. マイクロポストのContentに141文字以上を入力した状態で、マイクロポストを作成してみましょう。リスト 2.13で加えたバリデーションが本番環境でもうまく動くかどうか、確認してみてください。
2.4 最後に
省略
2.4.1 本章のまとめ
省略
質問ある方どうぞ
駆け出しエンジニアの方が多いかと思います。私でもお力になれることもあるかもしれないので、何か質問ありましたら聞いて下さい。
- 投稿日:2020-10-25T14:23:15+09:00
【Rails】sessionによるデータの一時保持
はじめに
sessionは「ウィザード形式」等でデータを保持させた状態で遷移先に移動する時に使用します。
目次
- セッションについて
- 実装例
- 最後に
1. セッションについて
セッションは情報を一時的に記憶しておく仕組みです。
Railsにおいてセッションは、sessionというオブジェクトにハッシュのような形でデータが格納されます。例えば、会員登録の際ページが切り変わって進んでいくようなサイトを想定します。
この場合、セッションを使い入力した情報を一旦sessionに格納して、次のページに遷移してそこで展開させる必要があります。2実装例
前提
前提としてdeviseを用いてページを遷移しながら会員情報を保存する事を想定してます。
開発環境
ruby 2.6.5
rails 6.0.0
devise 4.7.32.1 データの保持
それでは実装してみますー
registrations_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController def new @user = User.new end def create @user = User.new(sign_up_params) unless @user.valid? render :new and return end session["devise.regist_data"] = {user: @user.attributes} @address = @user.build_address render "new_address" end他いろいろ記述してますがsessionに注目します。
session["devise.regist_data"] = {user: @user.attributes}{user: @user.attributes}はsessionにハッシュオブジェクトの形で情報を保持させたい時、attributesメソッドを用いてデータを整形しています。
つまり、attributesされた@userのデータがハッシュの状態でsessionに入ってます。
これで、session["devise.regist_data"]に入力情報が代入され、保持さてた状態になります。2.2 展開させる
次に、データを保持した状態でページを遷移して展開します。
registrations_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController #省略 def create_address @user = User.new(session["devise.regist_data"]["user"]) @address = Address.new(useraddress_params) if @address.valid? @user.build_address(@address.attributes) @user.save session["devise.regist_data"]["user"].clear sign_in(:user, @user) else render "new_address" end endこちらもいろいろ記述してますが一番上の@userに注目。
@user = User.new(session["devise.regist_data"]["user"])session["devise.regist_data"]["user"]について説明します。
{user: 〇〇}でデータを持すと ["user"]を使う必要がある。
また、sessionは配列で情報を入れるので以下のように情報を持す事もできます。
session["devise.regist_data"] = {user: @user.attributes, address: @user.address}これで無事遷移先のページでsessionが展開できて@userに代入ができました。
あとは@userに代入されてるデータを使って保存などが可能です。
まとめ
今回は「ウィザード形式」でdeviseを用いた際のsessionの使いた方を紹介しました。
最後に
私はプログラミング初学者ですが、同じ様に悩んでる方々の助けになればと思い、記事を投稿しております。
それでは、また次回お会いしましょう〜
- 投稿日:2020-10-25T13:44:07+09:00
rubyで簡単に基本型と参照型を感じる2
rubyで簡単に基本型と参照型を感じる のメソッドを使わないパターンです。
macのターミナルでRubyを起動
irb参照型の値のもつ基本型の値を変更した場合
temp=[1,2] temp2=temp temp2[0]=11 temp #[11, 2]tempとtemp2の持つ値(参照値)は同じ
参照型の値そのものを変更した場合
temp=[1,2] temp2=temp temp2=11 temp #[1, 2]tempとtemp2の持つ値(参照値)は異なる
- 投稿日:2020-10-25T13:37:58+09:00
Unicorn環境でRubyをアップデートする
Unicorn と capistrano を使った Railsアプリケーション で ruby を 2.3 から2.5にアップデートしたので、
その手順をまとめてみました。1. rbenv の更新
updateしたい対象のrubyのバージョンをinstall し、global で対象のバージョンを指定しておく
$ rbenv install 2.5.8 $ rbenv global 2.5.8
rbenv install --list
でアップデートしたい対象のバージョンが出てこない時は、
rbenv install
ができないので、下記の手順でrbenvを更新すると、installできるはずです。$ cd ~/.rbenv/plugins/ruby-build $ git pull2. bundler の install と bundle install をしておく
デプロイ時に bundle install でこけないように予め bundler と 他のライブラリを install しておく
# gemfile.lock を確認して、同じ version の bundler を指定する $ gem install bundler -v 1.17.3 $ bundle install3. デプロイ
capistrano で通常通りデプロイする
しかし、この時必要なのが、 ruby のバージョンを切り替えるには、再起動が必要であり、
一度 unicorn を kill して立ち上げ直す必要があった。
preload_app: true
の設定をしている型は注意が必要です$ kill -QUIT `cat /path/to/unicorn.pid` $ bundle exec unicorn_rails -E production -D
- 投稿日:2020-10-25T13:04:26+09:00
Railsチュートリアル 第11章 SendGridでの送信者ID未認証による、メールが送信できない事象と対応
事象
Rails tutorialの「11.4 本番環境でのメール送信」において、herokuからユーザ登録のためのメール送信を実行してみたところ、「We're sorry, but something went wrong.」と表示された。
heroku logs
でログを確認すると、以下のようなエラーログが表示されており、
メールの内容等の情報が出力されていたので、SendGridによる送信処理の部分にエラーがありそう。Net::SMTPFatalError (550 The from address does not match a verified Sender Identity. Mail cannot be sent until this error is resolved. Visit https://sendgrid.com/docs/for-developers/sending-email/sender-identity/ to see the Sender Identity requirements)調査
現在チュートリアルの記載そのままで設定しているので、送信元として設定している「noreply@example.com」が認証されず、SendGridからメールが届かない?
ググってみるものの、Railsチュートリアルで同様の事象は見つけられなかった。試しに
heroku run rails console
より以下コマンドを実行するが、上記と同じエラーが発生し、メールが届かなかった。ActionMailer::Base.mail(from: "noreply@example.com", to: "<受信用メールアドレス>", subject: "subject", body: "body").deliver_nowログに示されたURL等を見て色々調べてみたが、SendGridでの送信元の認証が必要であるよう。
https://sendgrid.com/docs/for-developers/sending-email/sender-identity/
https://sendgrid.com/docs/ui/sending-email/sender-verification/ただ、「noreply@example.com」は自分で作成したドメインであるわけでもなく、このアドレスでの認証の仕方が不明なので、今回は個人用のアドレスを送信元に設定し、送信することとした。
対応
個人で使用しているメールアドレスを送信用メールアドレスとして、SendGridで送信元として認証し、使用する。
以下の記事を参考に、SendGridへログインし、「Single Sender Verification」を設定。
https://sendgrid.kke.co.jp/docs/Tutorials/B_Marketing_Mail/marketing_campaigns1.html認証後、以下のコマンドを実行すると、送信用メールアドレスから受信用メールアドレスにメールが届いた。
ActionMailer::Base.mail(from: "<送信用メールアドレス>", to: "<受信用メールアドレス>", subject: "subject", body: "body").deliver_nowそのため、メール送信部の処理は下記の通り変更し、テスト部の送信元も併せて変更したところ、heroku上で事象が解決し、メール送信が問題なく動作した。
/sample_app/app/mailers/application_mailer.rbclass ApplicationMailer < ActionMailer::Base default from: "<送信用メールアドレス>" layout 'mailer' end私見
実際のwebサービスでは、所有するメールアドレスやドメインを送信元として使用すると思われるため、所有しない「noreply@example.com」による送信は、解決を見送りました。
ただ、所有しないアドレスでの送信の可否等、まだまだ理解が追いついていないので、何か見つけた時は追記します。
- 投稿日:2020-10-25T12:25:03+09:00
rubyで簡単に基本型と参照型を感じる
- 投稿日:2020-10-25T01:45:41+09:00
Railsのvalid?とinvalid?
Ruby on Rails学習における忘備録です。
参照記事:Railsガイド
https://railsguides.jp/active_record_validations.htmlバリデーション
バリデーションは正しいデータをDBに保存するために行われる。
RailsはオブジェクトをActiveRecordオブジェクトに保存する前にバリデーションを実行する。
そこでエラーが発生するとオブジェクトは保存されない。
要は、DBの制約に対して保存条件を満たしていますか?のチェックをDBレベルで保存前に行う仕組みである。valid?とinvalid?
valid?はバリデーションを手動でトリガすることができる。
保存するオブジェクトにエラーがない場合はtrueを返し
エラーの際はfalseを返す。class User < ApplicationRecord # バリデーション(nameが空は許容しない) validates :name, presence: true end #これはtrue(条件を満たしている) User.create(name: "Gonshiba").valid? #これはfalse(条件を満たしていない→nameが空である) User.create(name: "").valid?invalid?はvalid?の逆チェックになっており、帰ってくるBool値が逆になるだけである。
ちなみに、createメソッドはオブジェクト作成から保存までを一括で行ってくれる。
- 投稿日:2020-10-25T00:38:03+09:00
いいね機能 非同期にする上で詰まった部分
対象者
いいね機能を実装している方で同期処理はできており、非同期処理の挙動とならない方
リロードしないと「いいねしている状態といいねしていない状態の切り替え」ができない方
Rails6におけるJQuery導入方法(①参照)やデバッグがうまくできていない(②参照)方環境
Rails6 環境
Turbolinks導入済み記事の目的
いいね機能において同期処理は実現できたが、非同期にする上で詰まった部分を記しておきたいと思います。
いいね機能の記事は良質なものがあるので(ただRails5の環境が多いかも?)そちらを基本にして頂き、本記事では初学者が詰まった時に参考になる部分があればいいなという思いで記事にしました。
また、初学者の為、間違いがあれば、ご指摘頂けると幸いです。
①Rails5とRails6におけるJQuery導入方法の違いを理解していなかった(環境設定ミス)
現在出ている記事ではRails5環境のものが多いのですが、Rails6環境での設定方法と異なることに注意しましょう。
Rails5の場合
Rails5系までは、「アセットパイプライン」を使用して、JavaScriptを管理します。
gem 'jquery-rails'application.js//= require jquery //= require rails-ujs以上のようにjquery-railsを導入することでRailsアプリケーション開発にJQueryを用いることができます。
その後、app/assets/javasctipts/application.jsなどのマニフェストファイル上でJQueryをrequireします。
私は、Rails6を導入していたのにも関わらず、このRails5の方法で記述してしまいました。
この部分に限らず、Railsのバージョンに気をつけて記事を参照して下さい。Rails6の場合
Rails6以降では「Webpacker」という機能で管理します。
私の場合、application.jsの配置場所はapp/javascript/packs/application.jsとなっていました。application.jsrequire("@rails/ujs").start() require("turbolinks").start() require('jquery')導入に関しては以下の記事を参考にしました。
https://techtechmedia.com/jquery-webpacker-rails/
加えてenvironment.jsの編集、JQuery導入の確認方法についてもこちらの記事で触れています。
JQuery導入の確認方法については、以下で少し触れます。JQuery導入の確認方法
application.jsに以下のコードを入れ、
application.jsrequire("@rails/ujs").start() require("turbolinks").start() require('jquery') document.addEventListener("turbolinks:load", () => { console.log($.fn.jquery) })remote:trueの記述によるJavascriptのリクエストによって呼び出されるjs.erbファイルにconsole.log($.fn.jquery)を追加。
create.js.erbconsole.log($.fn.jquery) $("#like_<%= @review.id%>").html("<%= j(render partial: "reviews/likes", locals: { review: @review, book: @book, likes: @likes })%>");そうすると、デベロッパーツールのconsoleの部分でJQueryのバージョンが表示されます。
JavaScriptで非同期処理を実装する際に確認する場所となるので覚えておきましょう。
consoleの部分に「3.5.1」のようにバージョンが表示されていれば、導入ができてるということになります。②変数定義のエラーに気づかなかった(rails sによるエラーのログを確認しなかった)
create.js.erb$("#like_<%= @review.id%>").html("<%= j(render partial: "reviews/likes", locals: { review: @review, book: @book, likes: @likes })%>");エラー出ていないと勘違いしていたのですが、rails sで出力されるログを確認しておりませんでした。
いいねボタンを押すとrails sのログが出力され、一番最後にidが未定義ですとエラーが出ていました。
rails sのログは、以下の通りです。Started POST "/books/2/reviews/3/likes" for ::1 at 2020-10-24 09:41:59 +0900 Processing by LikesController#create as JS ・ . (省略) ・ . ActionView::Template::Error (undefined method `id' for nil:NilClass): 1: $("#like_<%= @review.id %>").html("<%= j(render partial: "reviews/likes", locals: { review: @review, book: @book, likes: @likes })%>");一番下にエラーがでています。
私の場合は、コントローラ内の@reviewの定義方法の誤りで値が入っていないのが理由で、@reviewを含むcreate.js.erbが動作せず、非同期の挙動になりませんでした。
このエラーに対する適切なデバック(変数の正しい定義)を行ったら、リロードしないと「いいねしている状態といいねしていない状態の切り替え」ができなかった問題が解決され、非同期処理の挙動を実現することができました。同じような環境・部分で詰まっている方の参考になれば幸いです。
- 投稿日:2020-10-25T00:23:39+09:00
Rails学習 1日目その3
Ruby on Rails速習実践ガイド chapter3
3-1-4 データベースの環境ごとの使い分け
データベースには3種類の環境がある。
それぞれやることに対応してデータベースも割り振られている。
環境の種類 環境システム名 用途 開発環境 development 開発時の動作確認を行う テスト環境 test 自動テストを行う 本番環境 production ユーザーが利用可能な形で稼働させる 開発時はdevelopment環境とtest環境を使っていく。この二つのファイルはdb:createを実行した時に自動的に作成されるようになっている。
3-1-5 ビュー層を効率良く書くためにslimを使えるようにする(slimの使い方)
デフォルトではerbで行われているテンプレートエンジンをslimというテンプレートエンジンに変えると簡単にコードを書けるようになる。
slimの書き方
erbの書き方 slimの書き方 <% name %> - name <%= name %> = name #コメント /コメント <p> こんにちは </p> p こんにちは <a href='//example.com">image</a> a href="//example.com"image <div class="profile name"> .profile.name <div id="pam"> #pam slimは慣れると便利なので慣れるまでは苦労しそうだ。
3-2-2 タスクモデルのひな形を作成する
・モデルを作成する
モデルを作成するにはrails g の後に作成したいものを記入する(今回はmodel)$ rails g model Task name:string description:textこうすることで
・Taskモデルができる
・name:string description:textのテーブルを作成できる設計図のマイグレーションファイルができる
・モデルの自動テスト用のファイルができるテーブルを作るにはマイグレーションファイルをdb:migrateをするとテーブルが作られる。
3-3 コントローラーとビュー
・ルーティング
ルーティングはURLとHTTPメソッドを見てコントローラーの各アクションに割り振る。
URLの例 HTTPメソッド アクション名 機能名 役割 /tasks GET index 一覧表示 全タスクを表示する /tasks/17 GET show 詳細表示 特定のidのタスクを表示する /tasks/new GET new 新規登録画面 新規登録画面を表示する /tasks POST create 登録 登録処理を行う /tasks/17/edit GET edit 編集画面 編集画面を表示する /tasks/17 PATCH,PUT update 更新 更新処理を行う /tasks/17 DELETE destory 削除 削除処理を行う HTTPメソッドは重要!!
・ルーティングを1つにまとめる方法
get 'tasks/index' get 'tasks/show' get 'tasks/new' get 'tasks/edit'上のルーティングをresourcesを使ってまとめる
resource :tasksresourcesメソッドはindex,show,new,create,edit,update,destroyの7つのアクションを一つにまとめることができる
get "/" => "tasks#index" root to: 'tasks#index'またrailsの初期画面もroot toで指定するとそのアクションに連携したビューが初期画面になる。
・URLヘルパーメソッド
/tasks/newや/tasks/editなどもヘルパーメソッドに置き換えることができる
URLの例 HTTPメソッド URLパターン名 URLヘルパーメソッド /tasks GET tasks tasks_path /tasks/17 GET task task_path /tasks/new GET new_task new_task_path /tasks POST tasks tasks_path /tasks/17/edit GET edit_task edit_task_path /tasks/17 PATCH、PUT task task_path /tasks/17 DELETE task task_path 3-3-1-5 新規登録画面のビューを実装する
新規登録画面
app/views/tasks/new.html.slimh1 タスクの新規登録 .nav.justify-content-end = link_to '一覧', tasks_path, class: 'nav-link' =form_with model:@task,local:true do |f| .form-group =f.label :name =f.text_field :name, class: 'form-control', id: 'task_name' .form-group =f.label :name =f.text_area :description, rows: 5, class: 'form-control', id: 'task_description' =f.submit nil, class: 'btn btn-primary'form_withはフォームを作成するためのもの。フォームで必要なものをf.を使って記入していく。
・解説
.nav.justify-content-end:Bootstrapで用意されたコード
f.label :name :入力欄に対応するラベルを表示本来はnameだが日本語翻訳されているので名称という名前になる
=f.text_field :name:テキストを入れる場所
class: 'form-control:bootstrapに元々ついているクラス3-3-1-6 登録アクションを実装する
app/controllers/tasks_controller.rbdef create task = Task.new(task_params) task.save! redirect_to tasks_url, notice: "タスク「#{task.name}」を登録しました" end private def task_params params.require(:task).permit(:name,:description) endrequire(:task).permit(:name,:description)は「nameとdiscriptionの情報だけ許可して新しくtaskを作ります」という意味。paramsでその作ったパラメーターを取得する。
3-3-3 詳細表示機能を実装する
tasks/[タスクのid]はURLヘルパーメソッドを用いるとtask_path(task)と表すことができる。idはrailsの方で引数taskから自動的に判定されるようになっている。
3-3-3-2 詳細画面にタスクの属性情報を表示する
simple_format(h(@task.description),{},sanitize: false, wrapper_tag: "div")descriptionのように説明が入る文章は長くなり改行する必要が出てくる。simple_formatを用いることで簡単に改行が行われる。