20200414のRubyに関する記事は17件です。

-これからポートフォリオを作成する方へ- 使用した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.rb
require_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を使用されています。詳細はここでは省略させて頂きます。

●まとめ

面倒に感じますが、初期設定をしっかりと整えることでトータルで見ると工数をかなり減らすことができ、おすすめです。

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

時をかける引数

「引数って何だかよくわからない」
私がプログラミングを学習し始めて最初にぶち当たったのが、引数です。

今回はそんな引数を、映画:サマーウォーズで例えてみたいと思います。
時かけの千秋ファンの皆さま、タイトルで期待させてしまいすみません。

サマーウォーズとは、、
”オズ”と呼ばれる仮想世界が世界中の人々の暮らしに深く浸透している近未来を舞台に、数学以外には何のとりえもない高校生・健二と、彼の先輩・夏希とその親戚たちが、世界を救うために立ちはだかる!

作中で登場する人気キャラに「かずま」と呼ばれる13歳の引きこもりの少年がいます。
彼はオズの世界で「キングカズマ」というウサギ型のアバターを操り、なんと格闘技のチャンピオンなんです!

今回は、そんな

  • 仮想世界「オズ」
  • 引きこもり少年「かずま」
  • 最強アバター「キングカズマ」

を用いて、引数についてお話ししたいと思います。

作成するプログラムは、
「キングカズマが過去に戦った相手の名前と年齢を登録、管理するアプリ」です。
ちなみに、彼は負けなしなので勝敗は記録しません。最強ですからね。

まず引数を一言で説明すると、「引数はメソッドに渡す値のこと」です。
そして、引数にも2種類あって「実引数」と「仮引数」があります。

それぞれ、
メソッド:point_right:オズ
実引数:point_right:かずま
仮引数:point_right:キングカズマ
で考えていきますね。

まず「かずま」を誕生させます。データ登録するためには本人がいないと始まらないですよね。

summerWars.rb
kazuma = []

これは配列と呼ばれるものです。データを保管するための大きい箱だと思ってください。

次に「かずま」が「オズ」にログインするためのログイン画面を作ってあげないといけませんね。

summerWars.rb
while 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) :point_right: メソッド名(実引数)

続けて、オズの世界を作ります。

summerWars.rb
def oz(kingKazuma)
  battle = {}  #対戦相手を登録するためのハッシュを生成

end

これはメソッドと呼ばれるものです。
「oz」というメソッド名が呼ばれたらここに「kazuma」が転送されます。

ここでポイントがあります。

さっきまでoz(kazuma)だったのに、メソッド内ではoz(kingKazuma)になっています。
これは「かずま」が「キングカズマ」となって「オズ」の世界へログインしたということです。

お察しの良い方ならお気づきだと思いますが、
実引数と仮引数の中身は同じだが、メソッド内では呼び方を変えることができるんです。

例えば「かずま」がアバター名を「キングカズマ」ではなく「かずま」のままにしても問題ないですし、「とっとこハム太郎」という名前にしても同じ値を渡すことができます。
最強のとっとこハム太郎の誕生ですね。

そして、battle = {}という記述があります。
これはハッシュと呼ばれるもので、1つのデータを保管する小さい箱だと思ってください。
今回だと、1人の対戦相手を登録するための箱です。

次に、対戦相手の情報を入力する必要がありますね。名前と年齢です。
それぞれ、battle[:name] battle[:age]という名前をつけて保存しましょう。
これらの名前をキーといい、実際に中に入る値をバリューと呼びます。
基本的には「ハッシュの中にはキーとバリューがセット」で入っている、と覚えましょう。

情報を入力する時は、下記の方法を使います。
gets.chomp :point_right: 文字列で入力
gets.to_i :point_right: 整数で入力
これらを合わせると、

summerWars.rb
  battle[:name] = gets.chomp  #文字列を代入
  battle[:age] = gets.to_i    #整数を代入

という風に登録できますね。

データ自体の登録ができたら、今度は「キングカズマ」の戦歴にそれを登録する必要があります。

summerWars.rb
  kingKazuma << battle  #キングカズマの戦歴に登録

これは、配列にハッシュを登録しています。

このメソッドをまとめると、このようになります。

summerWars.rb
def oz(kingKazuma)
  battle = {}
  puts "対戦相手の名前を入力してください"
  battle[:name] = gets.chomp
  puts "対戦相手の年齢を入力してください"
  battle[:age] = gets.to_i

  kingKazuma << battle
  puts "登録完了しました!"
end

これで、「キングカズマ」の戦歴に数多の挑戦者たちの情報が刻まれるようになります。

次に、登録したデータを一覧で見たいですよね。
今回はその時のメソッドも作っちゃいましょう。

summerWars.rb
def battleResult(kingKazuma)
  puts "過去に対戦した一覧です"
  kingKazuma.each_with_index do |battle, index|
    puts "[#{index + 1}]名前:#{battle[:name]}\n"
  end

end

まず、先ほどと同じように「かずま」が「キングカズマ」として、今度は「battleResult」という世界にログインしました。
ここで見慣れないコードがありますが、きちんと説明しますのでタイムリープで逃げないでくださいね。

多分みなさんがよく見るのはこれですよね:point_down:

summerWars.rb
  kingKazuma.each do |battle|

そして今回登場したのはこれ:point_down:

summerWars.rb
  kingKazuma.each_with_index do |battle, index|

まず、each文は、「配列の中身を1つずつ取り出し、繰り返す」というものでしたよね。
しかし、今回はeach_with_indexという名前になってます。
これは基本的にやってることは同じなんですが、
取り出すと同時に要素の番号も引っ張ってきてくれる便利なやつなんです!
その代わり、引数の2つ設定してあげる必要があります。

summerWars.rb
  配列.each_with_index do |第一引数, 第二引数|

ちなみに、ここでも第一・第二引数の名前は何でもOKですが、わかりやすい名前にしておきましょう。

ここで第二引数を各要素に割り振る番号として設定しています。
それでは、もう一度先ほどのコードを見てみましょう。

summerWars.rb
  kingKazuma.each_with_index do |battle, index|
    puts "[#{index + 1}]名前:#{battle[:name]}\n"
  end

「ん???何でindexに + 1 してるの???」

これには理由があります。
配列の中に入っている要素は元々0から順番に番号が振られています。
つまり順番に取り出したら、0から始まってしまうのです。Re:ゼロからh

0から始まるリストよりも、1から始まった方が自然ですよね?
だから、初回のindexから1を足してるのです。
以下が、実際に出力される内容です。

summerWars.rb
過去に対戦した一覧です
[1]名前:タロウ
[2]名前:ジロー
[3]名前:サブロー

そして、このリストの番号を入力すれば、その詳細が見れるようにしたいです。
その場合は、下記のようにします。

summerWars.rb
  input = gets.to_i  #番号を入力し、inputへ代入

  battle = kingKazuma[input - 1]  #配列の中の要素番号を指定し、battleへ代入
  puts "名前:#{battle[:name]}、年齢:#{battle[:age]}"

ここで、inputを - 1してるのは、
元々の割り振られている番号は0のままだからです。
先ほど、+1した分をここで引かないといけませんからね。

このメソッドをまとめると、このようになります。

summerWars.rb
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

そして、最初のログインの画面で、戦歴一覧も選択できるようにしましょう。

summerWars.rb
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

これで、タイトル画面で、ログインと戦歴一覧を選択できるようになりました。

全てまとめると、下記のようなプログラムになります。おさらいしましょう。

summerWars.rb
def 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でプログラミング初学者に役立つ情報を発信していきますので、
今後とも、よろしくおねがいしまーーーーーーーーーす!!!!!!!!

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

heroku apps は 5個までらしいです。

Ruby on Rails チュートリアル 5.1(第4版)を学習中。
学習途中におきた出来事をつらつら備忘録として書いているチラシの裏のようなもの。
Qiita見切り発車。たぶん動くからリリースしようぜって偉い人が言ってた。
見にくさも技術も良くなっていくと思います。たぶん。知らんけど。

$heroku create

createコマンドを実行したら以下のとおり言われてしまった。

 ▸    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 apps
mysterious-atoll-47707
pure-hollows-45904
radiant-harbor-62133
rails-tutorial-nmhrgk

5個あったappが4個に減っている。
削除に成功したようだ。

$heroku create

無事新しいappを作成できたよ。

ちゃんちゃん。

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

rails 開発日記1 トップページの実装

 トップページの実装

*この記事は技術や知識の共有目的ではなく、完全に自己満足のアウトプット用です。

<開発環境>
1. ruby 2.6.3
2. Rails 5.1.6
3. AWS Cloud9
4. GitHub
5. Heroku(予定)

SNSのコピーサイトの開発にあたり、トップページを実装をした。
スクリーンショット 2020-04-14 16.14.22.png
                 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はあまり得意ではないのでこだわるつもりはないです。。。

次はユーザー登録機能の実装をしていきます。

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

active_hashを用いて都道府県データを表示してみた

なぜactive_hashを使うのか?

都道府県のデータなど、変更される可能性が低いものをハッシュとして管理できるため、わざわざテーブルを作成する必要が無い

というメリットがあります。

今回の内容はフリマアプリでactive_hashを利用した際に、つまずいた点があったので、
備忘録としてまとめたものです。

環境

Rails 5.2.3
Ruby 2.5.1

導入方法

gem active_hashをインストール

gem 'active_hash'

bundle installを実行。

1.png

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でも確認してみると

2.png

下は見切れてしまっていますが、取り出せているようです。

続いてviewファイル内のformの記述を編集し、ユーザーが都道府県を選択できるようにします。

viewファイル内を編集、都道府県データが表示されるか確認

= form_for @product do |f|

の記述から、= f.text_field, = f.text_areaといった形で入力フォームを作成しているので

f.collection_select :prefecture_id, Prefecture.all, :id, :name

フォームを設置したい箇所に上記を記述し、投稿してみる。

3.png
エラーが出てしまった....

binding.pryを用いて、prefectureが取り出せているか確認してみることに
5.png

4.png

しっかり取り出せている...

formの記述に誤りがあるのかもしれないとのことで、以下に変更

 = f.select :prefecture_id, Prefecture.all, :id, :name

6 .png

またしても、エラー...

form以外に怪しい点は無いか探してみると

なんとproductテーブル内にprefectureの入力を反映させるprefecture_idが存在していなかった。

これでは、中身があっても入れ物が無いようなもので、エラーが起きてしまうのも無理はない

no method errorは文字通りmethodが定義されていないというエラーでもあれば、もっと単純にコンピュータ側から「これがなんだかわからない」というメッセージでもあったことを知る。

カラムを追加して確認すると、、、
7.png

無事、実装完了。

終わりに

初めてactive_hash実装にあたって苦労する箇所は多かったが、使いこなすメリットは大きいと感じました。

ただエラー内容は単純なものだったこともあり、もう少し早く解決できた内容だったかなと...

まだまだ初学者の粋を脱することは難しいようだ

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

#Ruby Heredoc remove newline and indents ( ヒアドキュメントで改行とインデントを削除する )

Heredocを閉じたあとにメソッドチェーンするのではなくて、始まりにメソッドチェーンを書けることに驚いた

<<~DESC.gsub("\n", "")
  FOO
  BAR
DESC

=> "FOOBAR"

Original by Github issue

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

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

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
デプロイ先はHeroku

CI/CDの全体像

$ git push origin HEADでCircleCI作動

  1. bundle(依存関係)のインストール・リストア
  2. 静的コード解析 RuboCop
  3. テスト実行 RSpec
  4. Herokuにデプロイ(masterブランチのみ)

設定ファイル(コメントなし)

./.circleci/config.ymlに記述。
次項にコメント(個人メモ)ありver.

config.yml
version: 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

参考ドキュメント(日本語)

構築から少なくとも導入初期までは公式ドキュメントがオススメです。

言語ガイド:Ruby(Rails)
データベースの設定例
CircleCI を設定する(設定リファレンス)

CircleCIの用語やCI/CDの全体像を把握するのに役に立ったネット記事です。参考程度に。

「エンジニアのためのCI/CD再入門」連載一覧

Orbsの導入は、CircleCIドキュメントやGitHubなどを参考にするほうがオススメです。

sue445/ruby-orbs
CircleCI
GitHub

circleci/heroku
CircleCI
GitHub

参考までに私の記事もどうぞ。

既存RailsアプリにCircleCIを導入した手順
RailsアプリにDockerとCircleCIを導入した際、DB周りでエラーになったときの対処法

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

RiotとRRでテストケース書きたい

はじめに

突然見たことも触ったこともないruby(フレームワークなし)を触らなければならなくなってしまい、
かつなかなかニッチなテストフレームワークを使ってテストが書いてあるので、お勉強しました。。。

しんどい。。。
そもそもrubyの文法まったく知らない。。。

riot

http://thumblemonks.github.io/riot/
https://github.com/thumblemonks/riot

(riotて暴動って意味ですよね・・・)
他のテストフレームワークより簡素に書けて早いのがウリなユニットテストフレームワークです。ですって

sample1.rb
context "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.rb
context "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.rb
context "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.rb
class Calc
  def add(num1, num2)
    num1 + num2
  end
end
test1.rb
context "add result" do
    setup { 
     c = Calc.new
     c.add(5, 8)
    }
    # pass
    asserts{ topic }.equals{ 13 }
  end

◆asserts.equivalent_to 実際の値と期待値が同等かどうか
※微妙なテストケースです

User.rb
class User
    def initialize(name, age)
        @name = name
        @age = age
    end

    def do_something(str)
        action = str == "nothing" ? nil : str
    end
end
test2.rb
context "same objects?" do
    u = u2 = User.new("taro", 15)

    # pass
    asserts{ u }.equivalent_to(u2)
    end

◆asserts.nil 実際の値がnilかどうか ※期待値は不要
※テスト対象が上記Userクラス(do_something)なため省略

test3.rb
context "whats you do" do
    setup{
      u = User.new("taro", 15)
      u.do_something("nothing")
    }
    # pass
    asserts{ topic }.nil
  end

◆asserts.raises テストで予想される例外が発生しているか
※{}内で期待される例外が発生しているか

User.rb
class User

  # 意図的に例外を発生させる
  def raises_exception
        raise ArgumentError 
  end
test4.rb
context "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.rb
def return_arr(str1)
        array = []
        array.append(str1)
end
test5.rb
context "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.html

Use.rb
class User
  def do_something(str)
    action = str == "nothing" ? nil : str
    end
test6.rb
context "respond_to?" do

    # 生成したインスタンスにdo_somethingというメソッドがあるか
    # pass
    asserts{User.new("koji", 36)}.respond_to{:do_something}
end

◆asserts.size テスト結果のサイズが期待通りか

User.rb
class User
  def return_arr(str1)
        array = []
        array.append(str1)
  end
test7.rb
context "arrar size correct?" do
    setup{
      u = User.new("hiromu", 17)
      u.return_arr("hello")
    }

    # pass
    asserts{ topic }.size(1)
  end

◆asserts.empty テストの結果が空かどうか

User.rb
class User
  def return_something(str)
        re = str == "nothing" ? [] : str
  end
end
test8.rb
context "return empty?" do
    setup{
      u = User.new("taro", 20)
      u.return_something("nothing")
    } 

    # pass
    asserts{ topic }.empty   
  end

・hookup
テスト対象のtopicを変更することなく、topicを変更したい(例えば、インスタンス変数に新しい値を追加したい)ときに使います
hookupを使用すれば、↓と同様のことができます

sample4.rb
setup do
  topic.do_something
  topic
end
sample5.rb
context "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 thing

RR

https://www.rubydoc.info/gems/rr
ダブルルビーと言うらしいです。
テストダブル(モックとかスタブとか作る)のフレームワーク、みたいです。

・スタブ

stub1.rb
stub(スタブにしたいobject).スタブにしたいmethod_name {返したいreturn_value_}

スタブにしたいメソッドに引数を与えることもでき、与えられた引数以外が来た場合は動作しないようになっています。

stub2.rb
stub(スタブにしたいobject).スタブにしたいmethod_name( 引数 ) { 返したいreturn_value }

メソッド内で生成されるインスタンスをスタブにしたいときはこんな感じで作れます。

stub3.rb
any_instance_of(newするクラス) do |x|
        stub(x).スタブにしたい関数 { 返したい値 }
      end

・モック
モックはスタブと異なり、作成したモックがテストで呼ばれないとエラーになります。

mock1.rb
mock(モックにしたいobject).モックにしたいmethod_name {返したいreturn_value_}
mock2.rb
mock(モックにしたいobject).モックにしたいmethod_name( 引数 ) { 返したいreturn_value }

おわりに

実際にコード書ければよかったんですが、環境構築に時間がかかりそうだったのでいったんここで。。。

絶対書きます。。。

20200415追記:
riotを使用した簡易なソースを書きました。
次はrrを使ってモック・スタブを作りながら簡易なソースを書きます。

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

【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クラスの引数に入力してインスタンスを生成している。

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

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 => nilprivate メソッドの引数として渡しているから…ですかね。

もうよくわからない。

所感

とにかく =>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   # => 2

if 式で => を使う

# 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

参照

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

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

結構しょうもなかったが、色々勉強させてもらった

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

#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.0

gem 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.0

Original by Github issue

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

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

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を足すんだみたいに書いてあったりしたのも見かけましたが、それではうまくいかず調べていくうちにかなり腑に落ちる説明をできるようになったと思います。

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

商品一覧ページ内のカートに入れる機能の実装(非同期通信)

はじめに

商品一覧ページ内の各商品に割り振られているカートに入れるボタンが押された際にuser_idとeffector_id(現在作成中のアプリケーションではエッフェクターが商品のため各所でeffectorを使用していく)にログイン中のuserのidとクリックされた商品のidを保存することを目指す。今回は非同期通信を使用したい。

テーブル、コントローラー、APIの作成

cartsテーブルの作成

ターミナル
アプリケーションのディレクトリ$ rails g model cart

cartモデルの作成

(migrateファイル)create_carts.rb
class 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.rb
class Cart < ApplicationRecord
  belongs_to :effector
  belongs_to :user
end
(modelファイル)effector.rb
class Effector < ApplicationRecord
  belongs_to :genre
  has_many :users, through: :carts
  #今回はcartsコントローラーのshowアクションを使用するため上の記述は不要
  has_many :carts
end
(modelファイル)user.rb
class 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.rb
class 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.rb
Rails.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.js
function 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.rb
class 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回目の投稿のため拙い文章が眼に余るかと思いますがご精読ありがとうございました。駆け出しエンジニアのみなさん一緒に頑張っていきましょう。

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

商品一覧ページ内のカートに入れるボタンの実装(非同期通信)

はじめに

商品一覧ページ内の各商品に割り振られているカートに入れるボタンが押された際にuser_idとeffector_id(現在作成中のアプリケーションではエッフェクターが商品のため各所でeffectorを使用していく)にログイン中のuserのidとクリックされた商品のidを保存することを目指す。今回は非同期通信を使用したい。

テーブル、コントローラー、APIの作成

cartsテーブルの作成

ターミナル
アプリケーションのディレクトリ$ rails g model cart

cartモデルの作成

(migrateファイル)create_carts.rb
class 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.rb
class Cart < ApplicationRecord
  belongs_to :effector
  belongs_to :user
end
(modelファイル)effector.rb
class Effector < ApplicationRecord
  belongs_to :genre
  has_many :users, through: :carts
  #今回はcartsコントローラーのshowアクションを使用するため上の記述は不要
  has_many :carts
end
(modelファイル)user.rb
class 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.rb
class 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.rb
Rails.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.js
function 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.rb
class 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回目の投稿のため拙い文章が眼に余るかと思いますがご精読ありがとうございました。駆け出しエンジニアのみなさん一緒に頑張っていきましょう。

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

返信機能実装時、工夫した点 -newアクション-

投稿に対する返信機能を作成する際に、newアクションの実装を少し工夫することで便利にすることができました。

●実装コード

Q&Aサイトを実装しているため、投稿=Question 返信=Answerとなっています。

app/contorollers/answers_controlelr.rb
  def new
    @question = Question.find(params[:id])
    @answer = @question.answers.build
  end
config/routes.rb
  get  "/answers/:id", to: "answers#new", as: :new_answer
  resources :answers, except: %i(new)

●解説

1.アソシエーションでquestionを取得することができる。

app/contorollers/answers_controlelr.rb
  def new
    @question = Question.find(params[:id])
    @answer = @question.answers.build
  end

こうすることで、@answer = @question.answersとすることで、@answer.questionのような形で、questionを取得することができます。例えば下記のように活用したり。

app/contorollers/answers_controlelr.rb
def 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 となってしまい、パラメーターを渡すことができません。

●まとめ

あくまで自分はポートフォリオを作成している段階のレベル感です。
もっと良い実装方法や誤りがありましたら、指摘して頂けますと大変助かります。

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

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.html
doctype 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.html
script 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_usergonなどを使わずに渡すことが出来た。

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