- 投稿日:2020-04-14T23:57:31+09:00
-これからポートフォリオを作成する方へ- 使用したrails newを実行時のオプションを紹介
railsチュートリアルで学習した後にポートフォリオの作成に当たる方も一定数いらっしゃるかと思います。そこで、railsチュートリアルと評価されやすいポートフォリオの差を比較し、不要なファイルを生成しないためのrails_new実行時のオプションを紹介します。
●railsチュートリアルと異なる点
1.データベース
railsチュートリアルでは、特にデータベースの設定を行わず、デフォルトのまあSQliteを使用しています。
しかしポートフォリオを作成する際には、何らかのデータベースを選択し設定するかと思います。(MySQLかPostgreSQLが良いと思います。)そこで実行すべきコマンドが下記です。
rails new アプリケーション名 -d データベース名これにより使用するデータベースを任意の物に設定することができます。
もちろん後からでも変更できますが、工数を削減することができます。2.テスト
railsチュートリアルではminitesを使用していましたが、ポートフォリオして評価されるのであればRspecでテストを実装する必要があります。
そこで実行すべきコマンドが下記です。
rails new アプリケーション名 -Tこれによりminitestの作成をスキップできます。そしてrspecも自動生成ファイルを調整することができます。
config/application.rbrequire_relative 'boot' require 'rails/all' Bundler.require(*Rails.groups) module アプリケーション名 class Application < Rails::Application config.load_defaults 5.1 # 省略 config.generators do |g| g.test_framework :rspec, fixtures: false, # テストDBにレコード作成するファイルの作成をスキップ(初めだけ、のちに削除)。 view_specs: false, # ビューファイル用のスペックを作成しない。 helper_specs: false, # ヘルパーファイル用のスペックを作成しない。 routing_specs: false # routes.rb用のスペックファイル作成しない。 end end end詳細な設定方法は下記を参考に実装すると良いです。
Everyday Rails - RSpecによるRailsテスト入門少し情報が古い点もありますが、とても参考になります。
具体的にはcontroller_specやfeature_specは現在あまり使用されておらず、request_specやsystem_specを使用されています。詳細はここでは省略させて頂きます。●まとめ
面倒に感じますが、初期設定をしっかりと整えることでトータルで見ると工数をかなり減らすことができ、おすすめです。
- 投稿日:2020-04-14T19:44:43+09:00
時をかける引数
「引数って何だかよくわからない」
私がプログラミングを学習し始めて最初にぶち当たったのが、引数です。今回はそんな引数を、映画:サマーウォーズで例えてみたいと思います。
時かけの千秋ファンの皆さま、タイトルで期待させてしまいすみません。サマーウォーズとは、、
”オズ”と呼ばれる仮想世界が世界中の人々の暮らしに深く浸透している近未来を舞台に、数学以外には何のとりえもない高校生・健二と、彼の先輩・夏希とその親戚たちが、世界を救うために立ちはだかる!作中で登場する人気キャラに「かずま」と呼ばれる13歳の引きこもりの少年がいます。
彼はオズの世界で「キングカズマ」というウサギ型のアバターを操り、なんと格闘技のチャンピオンなんです!今回は、そんな
- 仮想世界「オズ」
- 引きこもり少年「かずま」
- 最強アバター「キングカズマ」
を用いて、引数についてお話ししたいと思います。
作成するプログラムは、
「キングカズマが過去に戦った相手の名前と年齢を登録、管理するアプリ」です。
ちなみに、彼は負けなしなので勝敗は記録しません。最強ですからね。まず引数を一言で説明すると、「引数はメソッドに渡す値のこと」です。
そして、引数にも2種類あって「実引数」と「仮引数」があります。それぞれ、
メソッドオズ
実引数かずま
仮引数キングカズマ
で考えていきますね。まず「かずま」を誕生させます。データ登録するためには本人がいないと始まらないですよね。
summerWars.rbkazuma = []これは配列と呼ばれるものです。データを保管するための大きい箱だと思ってください。
次に「かずま」が「オズ」にログインするためのログイン画面を作ってあげないといけませんね。
summerWars.rbwhile true do puts "Welcome to oz!" puts "[0]login" input = gets.to_i #数字を入力する if input == 0 #入力された値が0なら oz(kazuma) #オズの世界へログイン else puts "入力された値は無効です" end endここでは0が入力されると、「かずま」を「オズ」の世界へ連れていきます。
oz(kazuma) メソッド名(実引数)続けて、オズの世界を作ります。
summerWars.rbdef oz(kingKazuma) battle = {} #対戦相手を登録するためのハッシュを生成 endこれはメソッドと呼ばれるものです。
「oz」というメソッド名が呼ばれたらここに「kazuma」が転送されます。ここでポイントがあります。
さっきまで
oz(kazuma)
だったのに、メソッド内ではoz(kingKazuma)
になっています。
これは「かずま」が「キングカズマ」となって「オズ」の世界へログインしたということです。お察しの良い方ならお気づきだと思いますが、
実引数と仮引数の中身は同じだが、メソッド内では呼び方を変えることができるんです。例えば「かずま」がアバター名を「キングカズマ」ではなく「かずま」のままにしても問題ないですし、「とっとこハム太郎」という名前にしても同じ値を渡すことができます。
最強のとっとこハム太郎の誕生ですね。そして、
battle = {}
という記述があります。
これはハッシュと呼ばれるもので、1つのデータを保管する小さい箱だと思ってください。
今回だと、1人の対戦相手を登録するための箱です。次に、対戦相手の情報を入力する必要がありますね。名前と年齢です。
それぞれ、battle[:name]
battle[:age]
という名前をつけて保存しましょう。
これらの名前をキー
といい、実際に中に入る値をバリュー
と呼びます。
基本的には「ハッシュの中にはキーとバリューがセット」で入っている、と覚えましょう。情報を入力する時は、下記の方法を使います。
gets.chomp
文字列で入力
gets.to_i
整数で入力
これらを合わせると、summerWars.rbbattle[:name] = gets.chomp #文字列を代入 battle[:age] = gets.to_i #整数を代入という風に登録できますね。
データ自体の登録ができたら、今度は「キングカズマ」の戦歴にそれを登録する必要があります。
summerWars.rbkingKazuma << battle #キングカズマの戦歴に登録これは、配列にハッシュを登録しています。
このメソッドをまとめると、このようになります。
summerWars.rbdef oz(kingKazuma) battle = {} puts "対戦相手の名前を入力してください" battle[:name] = gets.chomp puts "対戦相手の年齢を入力してください" battle[:age] = gets.to_i kingKazuma << battle puts "登録完了しました!" endこれで、「キングカズマ」の戦歴に数多の挑戦者たちの情報が刻まれるようになります。
次に、登録したデータを一覧で見たいですよね。
今回はその時のメソッドも作っちゃいましょう。summerWars.rbdef battleResult(kingKazuma) puts "過去に対戦した一覧です" kingKazuma.each_with_index do |battle, index| puts "[#{index + 1}]名前:#{battle[:name]}\n" end endまず、先ほどと同じように「かずま」が「キングカズマ」として、今度は「battleResult」という世界にログインしました。
ここで見慣れないコードがありますが、きちんと説明しますのでタイムリープで逃げないでくださいね。多分みなさんがよく見るのはこれですよね
summerWars.rbkingKazuma.each do |battle|そして今回登場したのはこれ
summerWars.rbkingKazuma.each_with_index do |battle, index|まず、each文は、「配列の中身を1つずつ取り出し、繰り返す」というものでしたよね。
しかし、今回はeach_with_index
という名前になってます。
これは基本的にやってることは同じなんですが、
取り出すと同時に要素の番号も引っ張ってきてくれる便利なやつなんです!
その代わり、引数の2つ設定してあげる必要があります。summerWars.rb配列.each_with_index do |第一引数, 第二引数|ちなみに、ここでも第一・第二引数の名前は何でもOKですが、わかりやすい名前にしておきましょう。
ここで第二引数を各要素に割り振る番号として設定しています。
それでは、もう一度先ほどのコードを見てみましょう。summerWars.rbkingKazuma.each_with_index do |battle, index| puts "[#{index + 1}]名前:#{battle[:name]}\n" end「ん???何でindexに + 1 してるの???」
これには理由があります。
配列の中に入っている要素は元々0から順番に番号が振られています。
つまり順番に取り出したら、0から始まってしまうのです。Re:ゼロからh0から始まるリストよりも、1から始まった方が自然ですよね?
だから、初回のindexから1を足してるのです。
以下が、実際に出力される内容です。summerWars.rb過去に対戦した一覧です [1]名前:タロウ [2]名前:ジロー [3]名前:サブローそして、このリストの番号を入力すれば、その詳細が見れるようにしたいです。
その場合は、下記のようにします。summerWars.rbinput = gets.to_i #番号を入力し、inputへ代入 battle = kingKazuma[input - 1] #配列の中の要素番号を指定し、battleへ代入 puts "名前:#{battle[:name]}、年齢:#{battle[:age]}"ここで、inputを - 1してるのは、
元々の割り振られている番号は0のままだからです。
先ほど、+1した分をここで引かないといけませんからね。このメソッドをまとめると、このようになります。
summerWars.rbdef battleResult(kingKazuma) puts "過去に対戦した一覧です" kingKazuma.each_with_index do |battle, index| puts "[#{index + 1}]名前:#{battle[:name]}\n" end input = gets.to_i battle = kingKazuma[input - 1] puts "名前:#{battle[:name]}、年齢:#{battle[:age]}" endそして、最初のログインの画面で、戦歴一覧も選択できるようにしましょう。
summerWars.rbwhile true do puts "Welcome to oz!" puts "[0]login" puts "[1]result" input = gets.to_i if input == 0 oz(kazuma) elsif input == 1 battleResult(kazuma) else puts "入力された値は無効です" end endこれで、タイトル画面で、ログインと戦歴一覧を選択できるようになりました。
全てまとめると、下記のようなプログラムになります。おさらいしましょう。
summerWars.rbdef oz(kingKazuma) battle = {} puts "対戦相手の名前を入力してください" battle[:name] = gets.chomp puts "対戦相手の年齢を入力してください" battle[:age] = gets.to_i kingKazuma << battle puts "登録完了しました!" end def battleResult(kingKazuma) puts "過去に対戦した一覧です" kingKazuma.each_with_index do |battle, index| puts "[#{index + 1}]名前:#{battle[:name]}\n" end input = gets.to_i battle = kingKazuma[input - 1] puts "名前:#{battle[:name]}、年齢:#{battle[:age]}" end kazuma = [] while true do puts "Welcome to oz!" puts "[0]login" puts "[1]result" input = gets.to_i if input == 0 oz(kazuma) elsif input == 1 battleResult(kazuma) else puts "入力された値は無効です" end end以上で、キングカズマの戦歴管理アプリが完成しました。
今回のまとめは、
- 実引数と仮引数は同じ値だが、メソッド先では名前を任意で変更できる
- 各メソッド先でわかりやすい名前にする
ということでした。
何となくイメージはつきましたでしょうか?
ついたなら、私はとても嬉しいです。
値渡しと参照渡しの話もしたかったのですが、今回は割愛します。
一人でも多くの人に引数とキングカズマの強さが伝わると嬉しいです。これからもTwitterでプログラミング初学者に役立つ情報を発信していきますので、
今後とも、よろしくおねがいしまーーーーーーーーーす!!!!!!!!
- 投稿日:2020-04-14T19:02:56+09:00
heroku apps は 5個までらしいです。
Ruby on Rails チュートリアル 5.1(第4版)を学習中。
学習途中におきた出来事をつらつら備忘録として書いているチラシの裏のようなもの。
Qiita見切り発車。たぶん動くからリリースしようぜって偉い人が言ってた。
見にくさも技術も良くなっていくと思います。たぶん。知らんけど。$heroku createcreateコマンドを実行したら以下のとおり言われてしまった。
▸ You've reached the limit of 5 apps for unverified accounts. Delete some apps or ▸ add a credit card to verify your account.(訳)
ver未確認のアカウントのアプリ数の上限5に達しました。
一部のアプリを削除するかaccountクレジットカードを追加して、アカウントを確認します。無料のうちはアプリを5つ以上は作れないらしい。
$heroku apps上記コマンドで、現在作成したappを表示できた。
hmbrsnmhrgk mysterious-atoll-47707 pure-hollows-45904 radiant-harbor-62133 rails-tutorial-nmhrgk……確かに5個作成していた。
$heroku destroy --app hmbrsnmhrgk(アプリ名)古いappを1つ削除してみる。
▸ WARNING: This will delete ⬢ hmbrsnmhrgk including all add-ons. ▸ To proceed, type hmbrsnmhrgk or re-run this command with ▸ --confirm hmbrsnmhrgk(訳)
▸警告:これにより、すべてのアドオンを含む⬢hmbrsnmhrgkが削除されます。
proceed続行するには、hmbrsnmhrgkと入力するか、次のコマンドでこのコマンドを再実行します
▸-hmbrsnmhrgkを確認しますhmbrsnmhrgkを入力してみた。
$heroku appsmysterious-atoll-47707 pure-hollows-45904 radiant-harbor-62133 rails-tutorial-nmhrgk5個あったappが4個に減っている。
削除に成功したようだ。$heroku create無事新しいappを作成できたよ。
ちゃんちゃん。
- 投稿日:2020-04-14T17:52:08+09:00
rails 開発日記1 トップページの実装
トップページの実装
*この記事は技術や知識の共有目的ではなく、完全に自己満足のアウトプット用です。
<開発環境>
1. ruby 2.6.3
2. Rails 5.1.6
3. AWS Cloud9
4. GitHub
5. Heroku(予定)SNSのコピーサイトの開発にあたり、トップページを実装をした。
1、トップページの完成系自分なりに工夫した点
1.パーシャルの使用
パーシャルは開発時にHTMLの中身を整理する為のものである。
Railsチュートリアルを参考に、headerとIE用のHTML shimをまとめてみた。<!DOCTYPE html> <html> <head> <title>JiroApp</title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <%= render 'layouts/shim' %> </head> <body> <%= render 'layouts/header' %> <%= yield %> </body> </html>これによって、headerのHTMLを <_header.html.erb> に移行することができました。
HTML CSSはあまり得意ではないのでこだわるつもりはないです。。。
次はユーザー登録機能の実装をしていきます。
- 投稿日:2020-04-14T14:24:47+09:00
active_hashを用いて都道府県データを表示してみた
なぜactive_hashを使うのか?
都道府県のデータなど、変更される可能性が低いものをハッシュとして管理できるため、わざわざテーブルを作成する必要が無い
というメリットがあります。
今回の内容はフリマアプリでactive_hashを利用した際に、つまずいた点があったので、
備忘録としてまとめたものです。環境
Rails 5.2.3
Ruby 2.5.1導入方法
gem active_hashをインストール
gem 'active_hash'bundle installを実行。
active_hashのインストールが完了しましたので
続いてモデルの作成に移ります。
モデルを作成
まずはaddress.rbを作成します。
rails g model adderss作成したアドレス内に以下の内容のように編集する
class Address < ApplicationRecord extend ActiveHash::Associations::ActiveRecordExtensions belongs_to_active_hash :prefecture endこの記述により、後ほど作成するprefecture.rbとアソシエーションを組むことができ、prefecture.rbの内容を利用することができます。
rails db:migrateを行った後、prefecture.rbを作成します。
内容は以下のように編集しましょう
class Prefecture < ActiveHash::Base self.data = [ {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'}, {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'}, {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'}, {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'}, {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'}, {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'}, {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'}, {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'}, {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'}, {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'}, {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'}, {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'}, {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'}, {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'}, {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'}, {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'} ] endこれで、モデル内の準備は完了です。
次はコントローラー内を編集します。
コントローラーを編集
今回はフリマアプリなので、products_controller.rb内のnewアクション内に記述を行います。
def @address = Prefecture.all endこれにより@addressでprefectureを利用できるようになりました。
rails cでも確認してみると
下は見切れてしまっていますが、取り出せているようです。
続いてviewファイル内のformの記述を編集し、ユーザーが都道府県を選択できるようにします。
viewファイル内を編集、都道府県データが表示されるか確認
= form_for @product do |f|の記述から、= f.text_field, = f.text_areaといった形で入力フォームを作成しているので
f.collection_select :prefecture_id, Prefecture.all, :id, :nameフォームを設置したい箇所に上記を記述し、投稿してみる。
binding.pryを用いて、prefectureが取り出せているか確認してみることに
しっかり取り出せている...
formの記述に誤りがあるのかもしれないとのことで、以下に変更
= f.select :prefecture_id, Prefecture.all, :id, :nameまたしても、エラー...
form以外に怪しい点は無いか探してみると
なんとproductテーブル内にprefectureの入力を反映させるprefecture_idが存在していなかった。
これでは、中身があっても入れ物が無いようなもので、エラーが起きてしまうのも無理はない
no method errorは文字通りmethodが定義されていないというエラーでもあれば、もっと単純にコンピュータ側から「これがなんだかわからない」というメッセージでもあったことを知る。
無事、実装完了。
終わりに
初めてactive_hash実装にあたって苦労する箇所は多かったが、使いこなすメリットは大きいと感じました。
ただエラー内容は単純なものだったこともあり、もう少し早く解決できた内容だったかなと...
まだまだ初学者の粋を脱することは難しいようだ
- 投稿日:2020-04-14T12:09:56+09:00
#Ruby Heredoc remove newline and indents ( ヒアドキュメントで改行とインデントを削除する )
- 投稿日:2020-04-14T11:38:21+09:00
RailsアプリにCircleCI2.1でCI/CDを導入する設定ファイル【初心者向け】
こんにちは、ペーパーエンジニアのよしこです。
自作RailsアプリにCircleCIを使ってCI/CDを導入してから数週間が経過して、導入当初と比較して動作が安定しました。
私の場合、CircleCI公式ドキュメントを基本として自分の環境を構築しましたが、
当時はCircleCI version: 2.1の新機能※ に対応した他環境の設定ファイルを参考にしたいと思ってました。※ CicleCI vertsion: 2.1の新機能
orbs / commands / executors機能の説明は次の連載記事が分かりやすかったです。
「エンジニアのためのCI/CD再入門」連載一覧そこで今回、CI/CDを導入したい初心者エンジニア向けに、CircleCI2.1に対応した設定ファイルを公開したいと思います。
環境
Ruby 2.6.3 Rails 5.1.6 PostgreSQL 12.2 CircleCI 2.1 デプロイ先はHerokuCI/CDの全体像
$ git push origin HEAD
でCircleCI作動
- bundle(依存関係)のインストール・リストア
- 静的コード解析 RuboCop
- テスト実行 RSpec
- Herokuにデプロイ(masterブランチのみ)
設定ファイル(コメントなし)
./.circleci/config.yml
に記述。
次項にコメント(個人メモ)ありver.config.ymlversion: 2.1 orbs: ruby-orbs: sue445/ruby-orbs@1.6.0 heroku: circleci/heroku@1.0.1 workflows: build_test_and_deploy: jobs: - build - rubocop_job: requires: - build - rspec_job: requires: - build - deploy: requires: - rubocop_job - rspec_job filters: branches: only: - master executors: default: working_directory: ~/repository docker: - image: circleci/ruby:2.6.3-stretch-node environment: BUNDLE_PATH: vendor/bundle RAILS_ENV: test extended: working_directory: ~/repository docker: - image: circleci/ruby:2.6.3-stretch-node environment: BUNDLE_PATH: vendor/bundle PGHOST: 127.0.0.1 PGUSER: postgres RAILS_ENV: test - image: circleci/postgres:12-alpine environment: POSTGRES_USER: postgres POSTGRES_DB: app_name_test jobs: build: executor: default steps: - checkout - bundle-install rubocop_job: executor: default steps: - preparate - run: name: 静的コード解析を実行(RuboCop) command: bundle exec rubocop rspec_job: executor: extended steps: - preparate - run: name: DBの起動まで待機 command: dockerize -wait tcp://localhost:5432 -timeout 1m - run: name: DBをセットアップ command: bin/rails db:schema:load --trace - run: name: テストを実行(RSpec) command: | mkdir /tmp/test-results TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \ circleci tests split --split-by=timings)" bundle exec rspec --profile 10 \ --format RspecJunitFormatter \ --out test_results/rspec.xml \ --format progress \ $TEST_FILES - store_test_results: path: /tmp/test-results - store_artifacts: path: /tmp/test-results destination: test-results deploy: executor: heroku/default steps: - checkout - heroku/install - heroku/deploy-via-git commands: bundle-install: steps: - ruby-orbs/bundle-install: bundle_clean: true bundle_extra_args: '' bundle_gemfile: Gemfile bundle_jobs: 4 bundle_path: vendor/bundle bundle_retry: 3 cache_key_prefix: v1-bundle-dependencies restore_bundled_with: true preparate: steps: - checkout - bundle-install設定ファイル(個人メモあり)
config.yml# CircleCI 2.1 を使用 version: 2.1 # 公開されているCircleCI設定を読み込む。version: 2.1以上 orbs: ruby-orbs: sue445/ruby-orbs@1.6.0 heroku: circleci/heroku@1.0.1 # CI/CD工程の全体像 workflows: build_test_and_deploy: jobs: - build - rubocop_job: requires: - build - rspec_job: requires: - build - deploy: requires: - rubocop_job - rspec_job filters: branches: only: - master # 実行環境 executors: default: # コマンドを実行するディレクトリ(Gitリポジトリ名)を指定 working_directory: ~/repository docker: - image: circleci/ruby:2.6.3-stretch-node environment: # デフォルトはBUNDLE_PATH=/usr/local/bundleで設定。上書きが必要 BUNDLE_PATH: vendor/bundle RAILS_ENV: test extended: working_directory: ~/repository docker: - image: circleci/ruby:2.6.3-stretch-node environment: BUNDLE_PATH: vendor/bundle PGHOST: 127.0.0.1 # config/database.ymlの内容と一致させる PGUSER: postgres RAILS_ENV: test # $ psql -V で確認したバージョンと合わせる。-alpineが軽量版のため望ましい - image: circleci/postgres:12-alpine environment: # config/database.ymlの内容と一致させる POSTGRES_USER: postgres POSTGRES_DB: app_name_test # 各工程の定義 jobs: build: executor: default steps: # ソースコードを作業ディレクトリにチェックアウトする特別なステップ - checkout # 依存関係とバンドルの処理 - bundle-install rubocop_job: executor: default steps: - preparate # 静的コード解析を実行 - run: name: 静的コード解析を実行(RuboCop) command: bundle exec rubocop rspec_job: executor: extended steps: - preparate # DBの起動まで待機 - run: name: DBの起動まで待機 command: dockerize -wait tcp://localhost:5432 -timeout 1m # DBをセットアップ - run: name: DBをセットアップ command: bin/rails db:schema:load --trace # テストを実行 - run: name: テストを実行(RSpec) command: | mkdir /tmp/test-results TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \ circleci tests split --split-by=timings)" bundle exec rspec --profile 10 \ --format RspecJunitFormatter \ --out test_results/rspec.xml \ --format progress \ $TEST_FILES # テスト結果を保存 - store_test_results: path: /tmp/test-results - store_artifacts: path: /tmp/test-results destination: test-results deploy: executor: heroku/default steps: - checkout - heroku/install - heroku/deploy-via-git # 処理など。version: 2.1以上 commands: # 依存関係の処理(Orbsを利用) bundle-install: steps: - ruby-orbs/bundle-install: bundle_clean: true bundle_extra_args: '' bundle_gemfile: Gemfile bundle_jobs: 4 bundle_path: vendor/bundle bundle_retry: 3 cache_key_prefix: v1-bundle-dependencies restore_bundled_with: true # 各jobの準備工程 preparate: steps: - checkout - bundle-install参考ドキュメント(日本語)
構築から少なくとも導入初期までは公式ドキュメントがオススメです。
CircleCIの用語やCI/CDの全体像を把握するのに役に立ったネット記事です。参考程度に。
Orbsの導入は、CircleCIドキュメントやGitHubなどを参考にするほうがオススメです。
参考までに私の記事もどうぞ。
既存RailsアプリにCircleCIを導入した手順
RailsアプリにDockerとCircleCIを導入した際、DB周りでエラーになったときの対処法
- 投稿日:2020-04-14T11:02:33+09:00
RiotとRRでテストケース書きたい
はじめに
突然見たことも触ったこともないruby(フレームワークなし)を触らなければならなくなってしまい、
かつなかなかニッチなテストフレームワークを使ってテストが書いてあるので、お勉強しました。。。しんどい。。。
そもそもrubyの文法まったく知らない。。。riot
・http://thumblemonks.github.io/riot/
・https://github.com/thumblemonks/riot(riotて暴動って意味ですよね・・・)
他のテストフレームワークより簡素に書けて早いのがウリなユニットテストフレームワークです。ですってsample1.rbcontext "An empty Array" do setup { Array.new } asserts("it is empty") { topic.empty? } end # An Array・context
どんなテストをするのかの説明を書きます・setup
何をテスト対象とするのかを決めます
上記の場合だと{Array.new}で生成されたArrayがテスト対象になります※複数定義することもできます
複数定義した場合は上から評価され、次のsetupのtopic(↓参照)になります
2行目のsetupで生成したcheeseは3行目のsetupでtopicとして参照できるようになっていますsample2.rbcontext "A cheesey order" do setup { Cheese.create!(:name => "Blue") } setup { Order.create!(:cheese => topic, :purchase_order => "123-abc") } asserts_topic.kind_of(Order) # I love tests that are readable end・topic
setupで定義されたものをtopicと言います
今回の例だと、生成したArrayにアクセスしたい際はtopicを通してアクセスしますsample3.rbcontext "A string" do setup { "foo" } setup { topic * 2 } asserts(:length).equals(6) end複数setupを定義した際、3行目のsetup(topic * 2 (= "foofoo"))では、topicを通して2行目のsetupで定義したtopic("foo")にアクセスしています
・asserts
アサーションしたい時には組み込みのAssertionMacroクラスを使用してアサーションします。よく使いそうなものを抜粋してちょろっとコードを:
◆asserts.equals 実際の値と期待値が等しいかどうか
Calc.rbclass Calc def add(num1, num2) num1 + num2 end endtest1.rbcontext "add result" do setup { c = Calc.new c.add(5, 8) } # pass asserts{ topic }.equals{ 13 } end◆asserts.equivalent_to 実際の値と期待値が同等かどうか
※微妙なテストケースですUser.rbclass User def initialize(name, age) @name = name @age = age end def do_something(str) action = str == "nothing" ? nil : str end endtest2.rbcontext "same objects?" do u = u2 = User.new("taro", 15) # pass asserts{ u }.equivalent_to(u2) end◆asserts.nil 実際の値がnilかどうか ※期待値は不要
※テスト対象が上記Userクラス(do_something)なため省略test3.rbcontext "whats you do" do setup{ u = User.new("taro", 15) u.do_something("nothing") } # pass asserts{ topic }.nil end◆asserts.raises テストで予想される例外が発生しているか
※{}内で期待される例外が発生しているかUser.rbclass User # 意図的に例外を発生させる def raises_exception raise ArgumentError endtest4.rbcontext "raises Argument Exception?" do setup{u = User.new("kono taro", 55)} # assertsブロックに例外が発生する処理を書く # fail asserts{ u.raises_exception }.raises{ NameError } # pass asserts{ u.raises_exception }.raises{ ArgumentError } end◆asserts.kind_of 予想される型で結果が返ってきているか
User.rbdef return_arr(str1) array = [] array.append(str1) endtest5.rbcontext "return type is Array?" do setup{ u = User.new("hanako", 12) u.return_arr("hi") } # pass asserts{ topic }.kind_of{ Array } end◆assert.respond_to テストの結果が、指定されたメソッドに応答するオブジェクトかどうか
※Object#respond_to?のような感じです
https://docs.ruby-lang.org/ja/latest/method/Object/i/respond_to=3f.htmlUse.rbclass User def do_something(str) action = str == "nothing" ? nil : str endtest6.rbcontext "respond_to?" do # 生成したインスタンスにdo_somethingというメソッドがあるか # pass asserts{User.new("koji", 36)}.respond_to{:do_something} end◆asserts.size テスト結果のサイズが期待通りか
User.rbclass User def return_arr(str1) array = [] array.append(str1) endtest7.rbcontext "arrar size correct?" do setup{ u = User.new("hiromu", 17) u.return_arr("hello") } # pass asserts{ topic }.size(1) end◆asserts.empty テストの結果が空かどうか
User.rbclass User def return_something(str) re = str == "nothing" ? [] : str end endtest8.rbcontext "return empty?" do setup{ u = User.new("taro", 20) u.return_something("nothing") } # pass asserts{ topic }.empty end・hookup
テスト対象のtopicを変更することなく、topicを変更したい(例えば、インスタンス変数に新しい値を追加したい)ときに使います
hookupを使用すれば、↓と同様のことができますsample4.rbsetup do topic.do_something topic endsample5.rbcontext "A Person" do setup { Person.new(:name => "Master Blasterr") } denies(:valid?) # :( context "with valid email" do # ここをsetupにしてしまうと、topicがpersonではなく、person.emailになってしまう # テスト対象はpersonなので、topicがpersonに向く必要がある # hookupを使用するとsetupと同様のことを行うが、返すtopic(person)は変わらない hookup { topic.email = "master@blast.err" } asserts(:valid?) # Yay! end # with valid email end # A complex thingRR
https://www.rubydoc.info/gems/rr
ダブルルビーと言うらしいです。
テストダブル(モックとかスタブとか作る)のフレームワーク、みたいです。・スタブ
stub1.rbstub(スタブにしたいobject).スタブにしたいmethod_name {返したいreturn_value_}スタブにしたいメソッドに引数を与えることもでき、与えられた引数以外が来た場合は動作しないようになっています。
stub2.rbstub(スタブにしたいobject).スタブにしたいmethod_name( 引数 ) { 返したいreturn_value }メソッド内で生成されるインスタンスをスタブにしたいときはこんな感じで作れます。
stub3.rbany_instance_of(newするクラス) do |x| stub(x).スタブにしたい関数 { 返したい値 } end・モック
モックはスタブと異なり、作成したモックがテストで呼ばれないとエラーになります。mock1.rbmock(モックにしたいobject).モックにしたいmethod_name {返したいreturn_value_}mock2.rbmock(モックにしたいobject).モックにしたいmethod_name( 引数 ) { 返したいreturn_value }おわりに
実際にコード書ければよかったんですが、環境構築に時間がかかりそうだったのでいったんここで。。。
絶対書きます。。。20200415追記:
riotを使用した簡易なソースを書きました。
次はrrを使ってモック・スタブを作りながら簡易なソースを書きます。
- 投稿日:2020-04-14T10:37:23+09:00
【Rails】date_selectを使ってみた
学習メモです
タスクに期限を設定したくてカレンダーだと面倒なので日付選択が簡単なdate_selectを使ってみた。
型
html.slim
= f.date_select(:カラム名, use_month_numbers: true, start_year: Date型で最初の日付, end_year: Date型で終了の日付(interger型なので+5などでつければOK), default: 何も入力しなかった場合の値, date_separator: '区切りかた')実際に書いたコード
= f.date_select(:start_at, use_month_numbers: true, start_year: Date.today.year, end_year: Date.today.year + 5, default: Date.today, date_separator: '/')今paramsになにが来ているのかbinding.pryで見てみる
<ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"e12hjdLpjd/M5iuOGCkPaK5gZE6oR5US4HLybq1zKMNP178svzeGhjg6y2a3Fu6XnrlFuIKnkKtJgvIFqs6rkQ==", "task"=>{"name"=>"", "description"=>"", "start_at(1i)"=>"2020", "start_at(2i)"=>"2", "start_at(3i)"=>"30", "priority"=>"low", "completed"=>"doing"}, "commit"=>"Create Task", "controller"=>"tasks", "action"=>"create"} permitted: false>ちゃんと"start_at(1i)"=>"2020", "start_at(2i)"=>"2", "start_at(3i)"=>"30"がきていたのでこれをsaveする。
controllerのcreateアクションで
time = Date.new(params[:task]["start_at(1i)"].to_i,params[:task]["start_at(2i)"].to_i,params[:task]["start_at(3i)"].to_i) if taime.save redirect_to :index else render :new endこれはparamsに入っているhashから要素を取り出しDateクラスの引数に入力してインスタンスを生成している。
- 投稿日:2020-04-14T10:29:33+09:00
Ruby の右代入とエンドレスメソッド定義文を組み合わせるといろいろとつらいことがわかった
ブログ記事からの転載です。
さてさて、先日 Ruby の開発版に右代入とエンドレスメソッド定義構文が入りました。
この構文については以下を参照してください。この2つの機能を組み合わせて使うといろいろとつらいことがわかってきたのでちょっとまとめてみました
def hoge(value) = value => @value
アクセッサ的に引数をそのままインスタンス変数に代入するコードはよく記述すると思います。
それを右代入とエンドレスメソッド定義で書くとdef hoge(value) = value => @valueのように記述する事ができます。
もうこの時点でよくわからん!!みたいなユーザがいると思うんですがまだ意図としてはわかりやすいと思います。
しかし、これは意図した動作を行いませんでした。def hoge(value) = value => @value # 42 が @value に代入されてほしい p hoge 42 # しかし実際には @value にはなぜか :hoge が代入されている p @value # => :hogeこれはなぜかというと
def =
よりも=>
の方が優先順位が低く(def hoge(value) = value) => @value
と解釈されている(と予想)からです。
なので(def hoge(value) = value)
の結果である:hoge
が@value
へと代入されています。
ただし、この挙動はすでに修正されており最新版では以下のように意図する動作となります。def hoge(value) = value => @value # 42 が @value に代入される p hoge 42 p @value # => 42となります。
以降は以下の状態で動作確認しています。
p RUBY_VERSION # => "2.8.0" p RUBY_DESCRIPTION # => "ruby 2.8.0dev (2020-04-13T13:57:10Z master c28e230ab5) [x86_64-linux]"
private def hoge(value) = value => @value
private def hoge(value) = value => @value
のように書くこともできます。
これはhoge
メソッドをprivate
メソッドとして定義したいという意図になります。
これも先程の修正コミットよりも前はシンタックスエラーになっていたんですが現状では動作するようになっています。class X # メソッドを定義しつつ private メソッドにする private def hoge(value) = value => @value def foo hoge(42) p @value # => 42 end end p X.new.foo
def hoge(value) = value => @value => method_name
意図としては
def hoge
の結果をmethod_name
に代入したい…という感じなんですがもうよくわかりませんね…。
これは以下のような動作になります。def hoge(value) = value => @value => method_name hoge 42 p method_name # => :hoge p @value # => 42意図する動作にはなっています、が…。
private def hoge(value) = value => @value => @value2
もう何がしたいのかよくわかりませんね…。
意図としてはvalue
を@value
と@value2
の2つの変数に代入したいってことですが…。
これは実行時エラーになります。# error: `private': {:hoge=>nil} is not a symbol nor a string (TypeError) private def hoge(value) = value => @value => @value2これは
private((def hoge(value) = value => @value) => @value2)と解釈されて
:hoge => nil
をprivate
メソッドの引数として渡しているから…ですかね。
もうよくわからない。所感
とにかく
=>
とdef =
の優先順位がつらいって感じがしますね。
個人的には=>
もdef =
も=
ぐらい優先順位が低いとわかりやすいのかあ、とは思うんですが=>
が Hash の定義として使えるのでいろいろとつらそう…。
このあたりを解決するには=>
の記号を変えるしかなさそうなのかなあ…。
ちなみに知り合いはdef hoge(value) = @value = value
みたいに=
が連なってるのもつらいと言っていたのでdef =
も記号としてはつらそう。
うーん、左から右に流れるようにかけるので書いてて気持ちよくはあるんですが上で書いたように凝った書き方をすると意図しない動作になりそうなのできびしそうですねえ…。その他
42 => result1 => result2
42 => result1 => result2 p result1 # => 42 p result2 # => 42
[1, 2] => result1, result2
[1, 2] => result1, result2 p result1 # => 1 p result2 # => 2if 式で
=>
を使う# OK if result = value end # Error if value => result end
42 => a = b => c
これはシンタックスエラーになります。
# syntax error, unexpected '=', expecting end-of-input 42 => a = b => cメソッドにネストした
=>
を渡すdef hoge(h) p h end value = 42 # OK : Hash 渡しになる hoge :key => value # => {:key=>42} # NG : syntax error, unexpected =>, expecting end-of-input hoge 42 => value => value参照
- 投稿日:2020-04-14T10:13:20+09:00
railsのversion確認でエラーが出た時
$gem install rails -v 5.2.3
でinstallして
$rails -v
でversion確認するとRails is not currently installed on this system. To get the latest version, simply type: $ sudo gem install rails You can then rerun your "rails" command.んんんん、さっきインストールしたよな?
ま、この通りやってみるか
$sudo gem install rails
実行また同じエラーが出ました
色々試してみました
http://tomoprog.hatenablog.com/entry/2017/02/03/015936
https://qiita.com/arashida/items/ae982df5e534fd4bc97a
上を参考にさせてもらって試してみました
けど全く同じエラー
そうだ、passを確認
$which rails
~/.rbenv/shims/rails
$which ruby
~/.rbenv/shims/rails
うん、全く同じ
rubyのversionを確認
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19]
出るやんどういう事?解決策
色々調べた結果、アプリを再起動してみた
そして
$rails -v
すると
Rails 5.2.4.1
結構しょうもなかったが、色々勉強させてもらった
- 投稿日:2020-04-14T09:34:20+09:00
#Ruby gem install で過去のバージョンを指定するには -v オプションでバージョン指定 ( 例: gem install rubocop -v 0.77 )
方法
- -v でバージョン指定してインストールする
- 他のバージョンがインストールされている場合は gem uninstall で削除する
動作の例
バージョン指定してインストール
$ gem install rubocop -v 0.77 Successfully installed rubocop-0.77.0 Parsing documentation for rubocop-0.77.0 Done installing documentation for rubocop after 6 seconds 1 gem installedバージョン確認
前に別バージョンもインストールしていたので、2個のバージョンが表示された$ gem list rubocop *** LOCAL GEMS *** rubocop (0.81.0, 0.77.0)複数バージョンがインストールされていると、新しいほうが使われそう?
$ rubocop -v 0.81.0コマンド単位でのバージョン指定もできそうだけど
$ rubocop _0.77_ -v 0.77.0gem use / gem switch みたいな切り替えコマンドが見当たらない
$ gem RubyGems is a sophisticated package manager for Ruby. This is a basic help message containing pointers to more information. Usage: gem -h/--help gem -v/--version gem command [arguments...] [options...] Examples: gem install rake gem list --local gem build package.gemspec gem help install Further help: gem help commands list all 'gem' commands gem help examples show some examples of usage gem help gem_dependencies gem dependencies file guide gem help platforms gem platforms guide gem help <COMMAND> show help on COMMAND (e.g. 'gem help install') gem server present a web page at http://localhost:8808/ with info about installed gems Further information: http://guides.rubygems.org使わない方を uninstall する
$ gem uninstall rubocop Select gem to uninstall: 1. rubocop-0.77.0 2. rubocop-0.81.0 3. All versions > 2 Successfully uninstalled rubocop-0.81.0過去バージョンだけになる
$ gem list rubocop *** LOCAL GEMS *** rubocop (0.77.0)過去バージョンが利用できるのがわかる
$ rubocop -v 0.77.0Original by Github issue
- 投稿日:2020-04-14T08:14:08+09:00
2つの日付の月数の差分の計算
仕事で月数の差分を出す必要があったのですが、なかなかこれだっていうのが見つからなかったので書きました。
月数の差分を出す
baseを基準日として差分を出すための過去の日付をpastとし、どちらもDate型とします。
baseとpast共に年(year)に12を掛けて年月を月数に直してから減算し、最後に調整値を加算することで算出できます。def diff_months(base, past) adjusted_value = base.day >= past.day ? 1 : 0 ((base.year * 12) + base.month) - ((past.year * 12) + past.month) + adjusted_value end調整値について
1を足す基準が基準日の日にちが過去日の日にち以上というのは実際に例を出したほうがわかりやすいので例示します。
ex. 基準日を2020年3月15日、差分を出す過去日を2019年3月16日とする場合
この場合はちょうど丸1年です。なので差分は12ヶ月となってほしいです。
基準日の日にちbase.dayが15日、過去日の日にちpast.dayが16日なので調整値は0となります。(24240(= 2020年*12) + 3) - (24228(= 2019年*12) +3) = 12調整値が0なら12ヶ月となります。
ex. 基準日を2020年3月16日、差分を出す過去日を2019年3月16日とする場合
この場合は2020年の3月16日は13ヶ月目の1日目です。なので差分は13ヶ月となってほしいです。
基準日の日にちbase.dayが16日、過去日の日にちpast.dayが16日なので調整値は1となります。(24240(= 2020年*12) + 3) - (24228(= 2019年*12) +3) + 1 = 13以上のように基準日の日にちが差分計算に用いる過去日の日にち以上の場合は調整用に1を足す必要があります。
以上です。とにかく1を足すんだみたいに書いてあったりしたのも見かけましたが、それではうまくいかず調べていくうちにかなり腑に落ちる説明をできるようになったと思います。
- 投稿日:2020-04-14T02:55:12+09:00
商品一覧ページ内のカートに入れる機能の実装(非同期通信)
はじめに
商品一覧ページ内の各商品に割り振られているカートに入れるボタンが押された際にuser_idとeffector_id(現在作成中のアプリケーションではエッフェクターが商品のため各所でeffectorを使用していく)にログイン中のuserのidとクリックされた商品のidを保存することを目指す。今回は非同期通信を使用したい。
テーブル、コントローラー、APIの作成
cartsテーブルの作成
ターミナルアプリケーションのディレクトリ$ rails g model cartcartモデルの作成
(migrateファイル)create_carts.rbclass CreateCarts < ActiveRecord::Migration[5.0] def change create_table :carts do |t| t.references :user, foreign_key: true t.references :effector, foreign_key: true t.timestamps end end endマイグレーションファイルの編集
ターミナルアプリケーションのディレクトリ$ rails db:migrateマイグレーションの実行
(modelファイル)cart.rbclass Cart < ApplicationRecord belongs_to :effector belongs_to :user end(modelファイル)effector.rbclass Effector < ApplicationRecord belongs_to :genre has_many :users, through: :carts #今回はcartsコントローラーのshowアクションを使用するため上の記述は不要 has_many :carts end(modelファイル)user.rbclass User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :boards has_many :effectors, through: :carts #今回はcartsコントローラーのshowアクションを使用するため上の記述は不要 has_many :carts end各モデル内にリレーションを記載
APIの準備
app/controller/api/carts_controller.rbを作成
app/controller/api/carts_controller.rbclass Api::CartsController < ApplicationController def create Cart.create(user_id: params[:user_id], effector_id: params[:effector_id]) #json形式で送られてきたデータからparams[:user_id]をuser_idカラムにparams[:effector_id]をeffector_idカラムに保存 end endルーティング設定
routes.rbRails.application.routes.draw do root to: "effectors#index" devise_for :users resources :users, only: [:show, :edit, :update] resources :boards resources :effectors do resources :genres end resources :carts, only: [:show, :destroy] namespace :api do resources :carts, only: :create, defaults: { format: 'json'} end #cartsコントローラーとapp/controller/api/carts_controller.rbのルーティング設定 endここまで設定が終わったら商品一覧のカートに入れるボタンが押された際にcartsテーブルにuser_idとeffector_idが保存されるようにjsを記述していく。
商品一覧のカートに入れるボタンが押された時の処理
カートに入れるボタンが押された時onclick:を用いてjs内の関数を発火できるように記述する。
app/views/effectors/index.html.haml.main .main__list - @effectors.each do |effector| .main__list__effector{data: {genre: effector.genre.id}} .main__list__effector__info .main__list__effector__info__visual %h.main__list__effector__info__visual__name = effector.name .main__list__effector__info__visual__image %img{alt: "image1", class: "main__list__effector__info__visual__image__file", src: "#{- effector.image1}"} %img{alt: "image2", class: "main__list__effector__info__visual__image__file", src: "#{- effector.image2}"} .main__list__effector__info__text %h.main__list__effector__info__text__genre = effector.genre.genre %h.main__list__effector__info__text__point = effector.point pt %br %h.main__list__effector__info__text__detail = effector.text .main__list__effector__info__btns - if effector.youtube != nil .main__list__effector__info__btns__video %button.main__list__effector__info__btns__video__btn{onclick: "test(#{effector.youtube})"} 動画を視聴 - if effector.link != nil .main__list__effector__info__btns__official = link_to "#{effector.link}", class: "main__list__effector__info__btns__official__btn" do .main__list__effector__info__btns__official__btn__text 公式サイト - if user_signed_in? .main__list__effector__info__btns__cart %button.main__list__effector__info__btns__cart__btn{onclick: "createCart(#{current_user.id},#{effector.id})"} -#クリックされた時関数名createCartを引数(current_user.id,effector.id)とし発火 カートに入れる .popup .popup__content .popup__content__youtube %iframe(width="560" height="315" id = "youtube_test" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen) %button#close 閉じる発火される関数の記述
effectors.jsfunction createCart(userId, effectorId) { var param = { user_id: userId, effector_id: effectorId //受け取った引数をuser_id:とeffector_id:として変数paramに格納 } ajaxRequest("api/carts",'post',param) //非同期通信する際の関数を複数回の利用を見越して外出しして呼び出す(引数をapi/carts,post,paramとし呼び出し) } function ajaxRequest(url,type,data) { $.ajax({ url: url, //url: api/cartsと同義 type: type, //type: postと同義 dataType: 'json', data: data //data: paramと同義 //引数で持ってきた仮引数url,type,dataを使用しajaxの記述をする }) .done(function() { alert('商品をカートに入れました') }) .fail(function() { alert('error'); }); }ここまできたらjson形式で送られたデータをapi側のコントローラーで保存してあげるだけである。
先ほど記述したapp/controller/api/carts_controller.rbを見てみるapp/controller/api/carts_controller.rbclass Api::CartsController < ApplicationController def create Cart.create(user_id: params[:user_id], effector_id: params[:effector_id]) #json形式で送られてきたデータからparams[:user_id]をuser_idカラムにparams[:effector_id]をeffector_idカラムに保存 end endすでに記載している通りにjosn形式で送られてきたデータからcartsテーブルのuser_idカラムにparams[:user_id]をeffector_idカラムにparams[:effector_id]を保存する記載をする。
これにてカートに入れるボタンの実装が完了します。終わりに
今回はカートに入れるボタンというショッピングサイトには欠かせない機能を非同期通信にて行えるように実装した。
ユーザーと商品という多対多という関係性、非同期通信でのデータの保存など初学者が苦労する機能の実装を行うことで力がつくのを感じました。
この機能はいいね機能などにも流用できると思うので学習する価値ありだと感じます。
2回目の投稿のため拙い文章が眼に余るかと思いますがご精読ありがとうございました。駆け出しエンジニアのみなさん一緒に頑張っていきましょう。
- 投稿日:2020-04-14T02:55:12+09:00
商品一覧ページ内のカートに入れるボタンの実装(非同期通信)
はじめに
商品一覧ページ内の各商品に割り振られているカートに入れるボタンが押された際にuser_idとeffector_id(現在作成中のアプリケーションではエッフェクターが商品のため各所でeffectorを使用していく)にログイン中のuserのidとクリックされた商品のidを保存することを目指す。今回は非同期通信を使用したい。
テーブル、コントローラー、APIの作成
cartsテーブルの作成
ターミナルアプリケーションのディレクトリ$ rails g model cartcartモデルの作成
(migrateファイル)create_carts.rbclass CreateCarts < ActiveRecord::Migration[5.0] def change create_table :carts do |t| t.references :user, foreign_key: true t.references :effector, foreign_key: true t.timestamps end end endマイグレーションファイルの編集
ターミナルアプリケーションのディレクトリ$ rails db:migrateマイグレーションの実行
(modelファイル)cart.rbclass Cart < ApplicationRecord belongs_to :effector belongs_to :user end(modelファイル)effector.rbclass Effector < ApplicationRecord belongs_to :genre has_many :users, through: :carts #今回はcartsコントローラーのshowアクションを使用するため上の記述は不要 has_many :carts end(modelファイル)user.rbclass User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :boards has_many :effectors, through: :carts #今回はcartsコントローラーのshowアクションを使用するため上の記述は不要 has_many :carts end各モデル内にリレーションを記載
APIの準備
app/controller/api/carts_controller.rbを作成
app/controller/api/carts_controller.rbclass Api::CartsController < ApplicationController def create Cart.create(user_id: params[:user_id], effector_id: params[:effector_id]) #json形式で送られてきたデータからparams[:user_id]をuser_idカラムにparams[:effector_id]をeffector_idカラムに保存 end endルーティング設定
routes.rbRails.application.routes.draw do root to: "effectors#index" devise_for :users resources :users, only: [:show, :edit, :update] resources :boards resources :effectors do resources :genres end resources :carts, only: [:show, :destroy] namespace :api do resources :carts, only: :create, defaults: { format: 'json'} end #cartsコントローラーとapp/controller/api/carts_controller.rbのルーティング設定 endここまで設定が終わったら商品一覧のカートに入れるボタンが押された際にcartsテーブルにuser_idとeffector_idが保存されるようにjsを記述していく。
商品一覧のカートに入れるボタンが押された時の処理
カートに入れるボタンが押された時onclick:を用いてjs内の関数を発火できるように記述する。
app/views/effectors/index.html.haml.main .main__list - @effectors.each do |effector| .main__list__effector{data: {genre: effector.genre.id}} .main__list__effector__info .main__list__effector__info__visual %h.main__list__effector__info__visual__name = effector.name .main__list__effector__info__visual__image %img{alt: "image1", class: "main__list__effector__info__visual__image__file", src: "#{- effector.image1}"} %img{alt: "image2", class: "main__list__effector__info__visual__image__file", src: "#{- effector.image2}"} .main__list__effector__info__text %h.main__list__effector__info__text__genre = effector.genre.genre %h.main__list__effector__info__text__point = effector.point pt %br %h.main__list__effector__info__text__detail = effector.text .main__list__effector__info__btns - if effector.youtube != nil .main__list__effector__info__btns__video %button.main__list__effector__info__btns__video__btn{onclick: "test(#{effector.youtube})"} 動画を視聴 - if effector.link != nil .main__list__effector__info__btns__official = link_to "#{effector.link}", class: "main__list__effector__info__btns__official__btn" do .main__list__effector__info__btns__official__btn__text 公式サイト - if user_signed_in? .main__list__effector__info__btns__cart %button.main__list__effector__info__btns__cart__btn{onclick: "createCart(#{current_user.id},#{effector.id})"} -#クリックされた時関数名createCartを引数(current_user.id,effector.id)とし発火 カートに入れる .popup .popup__content .popup__content__youtube %iframe(width="560" height="315" id = "youtube_test" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen) %button#close 閉じる発火される関数の記述
effectors.jsfunction createCart(userId, effectorId) { var param = { user_id: userId, effector_id: effectorId //受け取った引数をキーuser_id:とキーeffector_id:にそれぞれのバリューとして変数paramに格納 } ajaxRequest("api/carts",'post',param) //非同期通信する際の関数を複数回の利用を見越して外出しして呼び出す(引数をapi/carts,post,paramとし呼び出し) } function ajaxRequest(url,type,data) { $.ajax({ url: url, //url: api/cartsと同義 type: type, //type: postと同義 dataType: 'json', data: data //data: paramと同義 //引数で持ってきた仮引数url,type,dataを使用しajaxの記述をする }) .done(function() { alert('商品をカートに入れました') }) .fail(function() { alert('error'); }); }ここまできたらjson形式で送られたデータをapi側のコントローラーで保存してあげるだけである。
先ほど記述したapp/controller/api/carts_controller.rbを見てみるapp/controller/api/carts_controller.rbclass Api::CartsController < ApplicationController def create Cart.create(user_id: params[:user_id], effector_id: params[:effector_id]) #json形式で送られてきたデータからparams[:user_id]をuser_idカラムにparams[:effector_id]をeffector_idカラムに保存 end endすでに記載している通りにjosn形式で送られてきたデータからcartsテーブルのuser_idカラムにparams[:user_id]をeffector_idカラムにparams[:effector_id]を保存する記載をする。
これにてカートに入れるボタンの実装が完了します。終わりに
今回はカートに入れるボタンというショッピングサイトには欠かせない機能を非同期通信にて行えるように実装した。
ユーザーと商品という多対多という関係性、非同期通信でのデータの保存など初学者が苦労する機能の実装を行うことで力がつくのを感じました。
この機能はいいね機能などにも流用できると思うので学習する価値ありだと感じます。
2回目の投稿のため拙い文章が眼に余るかと思いますがご精読ありがとうございました。駆け出しエンジニアのみなさん一緒に頑張っていきましょう。
- 投稿日:2020-04-14T00:22:06+09:00
返信機能実装時、工夫した点 -newアクション-
投稿に対する返信機能を作成する際に、newアクションの実装を少し工夫することで便利にすることができました。
●実装コード
Q&Aサイトを実装しているため、投稿=Question 返信=Answerとなっています。
app/contorollers/answers_controlelr.rbdef new @question = Question.find(params[:id]) @answer = @question.answers.build endconfig/routes.rbget "/answers/:id", to: "answers#new", as: :new_answer resources :answers, except: %i(new)●解説
1.アソシエーションでquestionを取得することができる。
app/contorollers/answers_controlelr.rbdef new @question = Question.find(params[:id]) @answer = @question.answers.build endこうすることで、@answer = @question.answersとすることで、@answer.questionのような形で、questionを取得することができます。例えば下記のように活用したり。
app/contorollers/answers_controlelr.rbdef create redirect_to question_path(@answer.question)2.@questionを取得することで、"answers/new" ページに質問の内容を記載することができる。
get "/answers/:id", to: "answers#new", as: :new_answer resources :answers, except: %i(new)こうすることで、/new/:question_id とすることができます。
この記述がないと、/new.question_id となってしまい、パラメーターを渡すことができません。●まとめ
あくまで自分はポートフォリオを作成している段階のレベル感です。
もっと良い実装方法や誤りがありましたら、指摘して頂けますと大変助かります。
- 投稿日:2020-04-14T00:12:51+09:00
RailsでGoogleAnalyticsを設定する
Railsで作成したアプリにGoogleAnalyticsを設定することがたくさんあると思う。
google-analytics-railsという便利なgemがあるそうだが、PRを見てみると2017年10月で終わっている…
多分メンテナンスされていないし、gemを使うほどでもないので自分で設定することにしたが、RailsからJavascriptへの変数の渡し方に手間取ったので残しておく。Analyticsタグの設置
GoogleAnalyticsタグは
head
に書いていく。今回はログインしている場合は変数current_user
をJavascriptに渡してあげて、ログインしていない場合は変数を渡さないという仕様にする。
このcurrent_user
をRailsからJavascriptに渡す時にかなり工夫した。コード
コードを書いていく。テンプレートエンジンはslimを使っている。
app/views/layouts/application.slim.htmldoctype html html head meta content=("text/html; charset=UTF-8") http-equiv="Content-Type" = csrf_meta_tags = stylesheet_link_tag 'application', media: 'all' = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' - if Rails.env.production? = render './google_analytics'Analyticsは本番環境だけで動かしたいので
if Rails.env.production?
の中に入れる。
Analyticsスクリプトは他のファイルでも使う可能性があり使いまわしたいので、部分テンプレートで共通化しておく。
先に答えを出すと、下記のようなコードになる。app/views/layouts/_google_analytics.slim.htmlscript async="" src="https://www.googletagmanager.com/gtag/js?id=#{トラッキングID}" javascript: window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'トラッキングID'); if (#{raw current_user.to_json}) { gtag('set', {'user_id': #{raw current_user.to_json}.id}); }RailsからJavascriptに変数を渡す場合、その変数のデータ型がintegerの場合はそのまま渡して問題ないが、それ以外の場合は
current_user.to_json
というようにto_json
をつけてあげないといけない。
また、エスケープ処理をさせないために、ActionView::Helpers::OutputSafetyHelper
で用意されているraw
メソッドを使用する。
こうすることで、Railsのcurrent_user
をgon
などを使わずに渡すことが出来た。