20200709のRailsに関する記事は16件です。

デバッグを表示する

何度も忘れるのでメモ。

デバッグを使う事でより開発がスムーズになる。
現在表示されているページのcontroller,view,actionなどが確認できる。
注意点として本番環境ではデバッグ情報は表示するべきではありません。

そのため、次のように記述することで開発環境のみデバッグ情報を表示されるよう対策しています。

まずはgem 'byebug'をbundle installする。
その際development、test環境で使用するのでgroup :development, :test do~end内に書き込む。

group :development, :test do
  gem 'sqlite3'
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end
$ bundle install

下記コードをapp/views/layouts/application.html.erbに書き込む。

<%= debug(params) if Rails.env.development? %>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></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' %>
    <div class="container">
      <%= yield %>
      <%= debug(params) if Rails.env.development? %>
    </div>
  </body>
</html>

これで表示、使用可能になります。

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

railsのbelongs_toに指定できるoptional: true

class Task < ApplicationRecord
  has_many :user
end


class User < ApplicationRecord
  belongs_to :task, optional: true
end

このoptional: trueとは何なのか?

これはbelongs_toの外部キーのnilを許可するというもの!

上記のコードであれば、User.task_idが外部キーとなり、値がセットされていない場合はバリデーションで弾かれるが、optional: trueを設定しておくと、外部キーがnilであってもDBに保存できる!

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

find_or_create_by

find_or_create_by

引数の条件に指定するデータがあった場合はそのデータを返します。無かった場合は新規作成します。

def find_or_create_by(attributes, &block)
  find_by(attributes) || create(attributes, &block) 
end

メリット

結果が冪等になる。
冪等とは何度行っても得られる結果が等しいこと。

すでに指定するデータが存在した場合は新規作成をしないので、
find_or_create_byを繰り返したとしても、得られる結果が等しいです。

対してcreateメソッドだった場合は、
繰り返した分だけ新規作成が行われるので冪等ではないと言えます。

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

RailsとAjaxを使ったいいね機能の非同期通信

転職活動用に個人アプリを開発中です。
今回、RailsとAjaxを使って、いいね機能の非同期化を行いました。

AjaxではjQueryを使うため、jQueryを使えるようにしておく事前準備が必要です。それは参考記事を見てください。以下の記述はそれが設定済みのうえでの話です。

PFCMASTERいいね機能Ajax.png

実現した機能

・「いいね」ボタンを押すとリロードせずに「いいねを取り消す」に表示が変わる
・「いいね」ボタンを押すとリロードせずにlikesテーブルにデータが1つ追加される
・「いいね」ボタンを押すとリロードせずにいいね数が1つ増える
※その逆もしかり

このコードでうまくいきました

コントローラー(likes_controller.rb)

likes_controller.rb
class LikesController < ApplicationController

  def create
    @post = Post.find(params[:post_id])
    @like = current_user.likes.build(post_id: params[:post_id])
    @like.save
    @likeCounts = Like.where(post_id: params[:post_id])
  end

  def destroy
    @post = Post.find(params[:post_id])
    @like = Like.find_by(post_id: params[:post_id], user_id: current_user.id)
    @like.destroy
    @likeCounts = Like.where(post_id: params[:post_id])
  end

end

個別の投稿ページ(上記の画像のページ)

show.html.haml
.like
  = render partial: "likes/like", locals: {post: @post}

部分テンプレート(_like.html.haml)

_like.html.haml
- if user_signed_in?
  - if current_user.already_liked?(post)
    = button_to 'いいねを取り消す', post_like_path(post_id: post.id, id: post.likes[0].id), method: :delete, remote: true
  - else
    = button_to 'いいね', post_likes_path(post.id), method: :post, remote: true
.likeCounts
  いいね数:
  = post.likes.count

更新したい部分のビュー(いいねしたとき)

create.js.erb
$('.like').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");

更新したい部分のビュー(いいねを取り消すとき)

destroy.js.erb
$('.like').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");

JavaScriptが動く仕組み

link_tobutton_toには:remoteオプション(remote: true)がある。button_toremote: trueを追加することで、js形式のリクエストを送信できるようになる。

= button_to 'いいね', post_likes_path(post.id), method: :post, remote: true

参考:Railsガイド

非同期通信の流れ

1.「いいね」ボタンを押す
2.下記のリンクでlikes#createにjs形式でリクエストが飛ばされる

= button_to 'いいね', post_likes_path(post.id), method: :post, remote: true

3.likesコントローラーのcreateアクションが動き、いいねが保存される。Likeモデルを介してデータベースに追加される(リロードせずに)
4.更新したい部分のページcreate.js.erb,destroy.js.erbがレスポンスとして返される。.html(jQueryのhtmlメソッド)は、.like(likeクラス)の部分をhtmlの後ろの( )内に置き換える役割をはたす。

今回苦労したところ

1.部分テンプレートの理解

create.js.erb
$('.like').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");

partialオプション:部分テンプレートの呼び出しを行う。今回はlikesフォルダの_like.html.hamlを呼び出したいのでlikes/likeとなる。

localsオプション:部分テンプレート内で{ }内の左辺が変数として使えるようになる。今回でいうとpostが変数として使えるようになる。右辺の@postは何かというと、右辺の@postが左辺のpostに代入して、それが変数として使えるようになる。右辺の@postはどこからきているかというと、postsコントローラのshowアクションで定義しているので、そこからきている。

posts_controller.rb
def show
  @post = Post.find(params[:id])
  Like.new
end

2._like.html.hamlbutton_toのpathの設定
ずっとこのエラーに悩まされていました。

ActionView::Template::Error No route matches (中略) missing required keys: [:id])

この記事を見つけてようやく下記のように記述して解決できました。

= button_to 'いいねを取り消す', post_like_path(post_id: post.id, id: post.likes[0].id), method: :delete, remote: true

解決はしたものの、この部分id: post.likes[0].idがまだちゃんと理解できていません。
1つの投稿に複数のいいねがついていたとして、その最初のいいねのidを取得している?

今考えてみると、確かに1つの投稿に複数のいいねがある場合、「どのいいねを取り消すの?指定してくれないとわからないよ」と言われても無理ないなと思いました。

だとすると、必ずしもcurrent_userの付けたいいねではなく、他の人の付けたいいねを取り消してしまう?
まだ修正する必要があるかもしれません?

(追記)
Sequel Proで確認したところ、他の人のいいねを取り消してしまうことはなく、ちゃんとcurrent_userのいいねが取り消されていました。

一応いいねを消せることは消せます。
この点が明らかになったらまた追記します。

参考記事

Railsで remote: true と js.erbを使って簡単にAjax(非同期通信)を実装しよう!(いいね機能のデモ付)
【Rails×Ajax】いいね機能ハンズオン
Railsでいいね機能を実装。Ajaxを使い非同期対応。で

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

Enumの設定ミス。なぜかEnum指定の列からデータが取れない…

発生した背景

※備忘録として記録します(ネタ的にはしょぼいです…)
以下のようなモデルにEnum設定したとき、データが取り出せない。

table.rb
class ColorManage < ApplicationRecord
  enum color_type: { single: 0, double: 1, graphic: 2 }
(省略)

(前提)singleというラジオボタンを押すと、0が入力されるようにする。

color-edit.html.erb
  <!--  カラータイプ、どの設定にするか。-->
  <%= color_manage_record.radio_button :color_type, :single ,class:"color-form__button" %>
  <%= color_manage_record.label :color_type, "single-color", {class: "color-form__tag"} %>

問題点> 画像のとおり、color_typeには"ゼロ"で入力されていますが、モデル抽出して、列値を取得しようとしてもnullで返ってくる

image.png

環境

項目 内容
OS.Catalina v10.15.4
Ruby v2.5.1
Ruby On Rails v5.2.4.3
MySQL V5.6

対応手順

原因)テーブルの定義がstringだったので、enumを以下のように定義しなおしました。ポートフォリオなので、恥ずかしながら、設計側を変えた次第です。

test.rb
  enum color_type: { single: "single", double: "double", graphic: "graphic" }

これで解決しました。

以上です。

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

Timeオブジェクトの次の値ってどうやってだすのが正しい?

概要

RubyでTimeオブジェクトを扱ってると境界問題にぶち当たる。
普通にやってるとRangeオブジェクトなりを使ってそこまで問題になることはないが、普通じゃないやり方をしていて躓いた。

環境

Ruby: 2.6.2
Rails: 5.1.2

そもそもハマった問題

Time.now.end_of_day
=> 2020-05-15 23:59:59 +0900

これの次の値を取りたい。
※ end_of_dayはわかりやすいから使ってるだけであって、とある日の先端、終端が欲しいわけではない。飽くまでもTimeオブジェクトの次の値

真っ先に思いついたのがこれ

Time.now.end_of_day + 1
=> 2020-05-16 00:00:00 +0900

ただ、これは間違い

(Time.now.end_of_day + 1).iso8601(3)
=> "2020-05-16T00:00:00.999+09:00"

+ 1 は飽くまでも1秒加算であって、次の値に移行するわけではないので使えない。

Timeクラスを漁ってるとすごくそれっぽいメソッドを見つけた
https://docs.ruby-lang.org/ja/latest/method/Time/i/succ.html

Time.now.end_of_day.succ
(pry):109: warning: Time#succ is obsolete; use time + 1
=> 2020-05-16 00:00:00 +0900

しかし、時代遅れだから使うなって怒られる上 + 1 と同じことしてるだけらしい。だめ。

解決方法

結局良さげな方法はわからなかったからゴリ押し

例の問題だと少数9桁までしか見ていない

(Time.now.end_of_day + 1).iso8601(10)
=> "2020-05-16T00:00:00.9999999990+09:00"

つまり、1ナノ秒加算してやればいいわけだ

(Time.now.end_of_day + 1/1000000000.0).iso8601(9)
=> "2020-05-16T00:00:00.000000000+09:00"

できた!

おまけ

Time.now.iso8601(10)
=> "2020-05-15T19:56:55.4979370000+09:00"

Time.new(2020, 5, 16, 16, 59, 59.3).iso8601(9)
=> "2020-05-16T16:59:59.299999999+09:00"

Time.parse("2020-05-16T16:59:59.3+09:00").iso8601(9)
=> "2020-05-16T16:59:59.300000000+09:00"

Time.parse("2020-05-16T16:59:59.35555555555555555555+09:00").iso8601(20)
=> "2020-05-16T16:59:59.35555555555555555555+09:00"

だめだった

結論

  • Timeオブジェクト内の小数秒の桁数は無限に持てる
  • そのため次の値という概念がそもそも無い
  • ただ、Timeオブジェクトの生成の仕方に依存するので、DBから引っ張ってくるならその有効桁数を考慮してやれば無理やり作れなくはない
  • そもそも素直にRangeオブジェクトを使うなりして比較すべき
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails5でECサイトを作る⑥ ~seedデータ投入~

はじめに

架空のベーカリーで買い物できるECサイトを作るシリーズ、Rails5でECサイトを作る⑤の続きです。
この後の実装を続けていく上で、初期データを入れておいた方が何かと都合が良さそうなので、一旦そちらを優先することにしました。

複数のseedファイルを管理する

あくまで表示が正しくできるか確認するためのデータなので、最小限作っておけば良いと思います。しかし、Model数が多い分、やはり一つでファイルで全て済まそうとすると記述量が膨大になってしまいます。
そこで、新たにseedsというフォルダを作り、その中に各Model毎の初期データを入れることにしました。(一つのseeds.rbファイルで済ませたい人はこちら

$ cd work/fumizuki/db
$ mkdir seeds
$ cd seeds
$ touch customer.rb
$ touch address.rb
$ touch genre.rb
$ touch product.rb
$ touch order.rb
$ touch order_item.rb

ファイル群の読み込み、Adminデータ作成

作成したファイル群をseeds.rbファイルから読み込めるようにします。これでrails db:seedを叩いた時にseedsフォルダ内のデータも認識されます。
ここで注意が必要な点は、親Modelを上に記述することです。ファイルの読み込みは上の行から順番に行われるため、子Modelを先にしてしまうと「Customerデータがありません!」とエラーになります。

db/seeds.rb
require './db/seeds/customer.rb'
require './db/seeds/address.rb'
require './db/seeds/genre.rb'
require './db/seeds/product.rb'
require './db/seeds/order.rb'
require './db/seeds/order_item.rb'

Admin.create!(
  id: 1,
  email: 'admin@user',
  password: 'adminuser',
  password_confirmation: 'adminuser'
  )

Adminのデータもこちらに書いておきました。管理者のデータは1件あれば十分で、わざわざ別ファイルに分けるほどのコード量ではないので。

データをたくさん作る

架空の人名架空の地名はいつものサイトで調達します。

Address

db/seeds/.rb
Address.create!(
  [
    {
      id: 1,
      customer_id: 1,
      addressee: '稲継亜矢子',
      post_code: '1111111',
      address: '月川県岩青山町四南寺2-15',
    },

    {
      id: 2,
      customer_id: 2,
      addressee: '鈴鹿由美子',
      post_code: '2222222',
      address: '細野県城見市世史が丘3-1-7',
    },

    {
      id: 3,
      customer_id: 3,
      addressee: '関沢智恵美',
      post_code: '3333333',
      address: '赤田県初山市十越智町6-12',
    },

    {
      id: 4,
      customer_id: 4,
      addressee: '巻譲',
      post_code: '4444444',
      address: '根野県羽島市後目台8-12',
    },

    {
      id: 5,
      customer_id: 5,
      addressee: '岡崎弥生',
      post_code: '5555555',
      address: '古岡県紫波市刈唯山3-5-2',
    },
  ]
  )

Customer

db/seeds/.rb
Customer.create!(
  [
    {
      id: 1,
      email: '1@1',
      family_name: '長岡', family_name_kana: 'ナガオカ',
      first_name: '聡美',   first_name_kana: 'サトミ',
      post_code: '1111111',
      address: '北岡県乙西川市馬城寺2-6-1',
      tel: '11111111',
      is_active: true,
      password: '111111',
      password_confirmation: '111111',
    },

    {
      id: 2,
      email: '2@2',
      family_name: '野崎', family_name_kana: 'ノザキ',
      first_name: '健吾',   first_name_kana: 'ケンゴ',
      post_code: '2222222',
      address: '鳥川県盤上山市升沖ヶ丘1-15',
      tel: '22222222',
      is_active: true,
      password: '222222',
      password_confirmation: '222222',
    },

    {
      id: 3,
      email: '3@3',
      family_name: '妹尾', family_name_kana: 'セオ',
      first_name: '千代美',   first_name_kana: 'チヨミ',
      post_code: '3333333',
      address: '仁田県那珂和町山巻学園3-2',
      tel: '33333333',
      is_active: true,
      password: '333333',
      password_confirmation: '333333',
    },

    {
      id: 4,
      email: '4@4',
      family_name: '塚越', family_name_kana: 'ツカゴシ',
      first_name: '貴美',   first_name_kana: 'タカミ',
      post_code: '4444444',
      address: '笠川県冬田市志林川町5-2-7',
      tel: '44444444',
      is_active: true,
      password: '444444',
      password_confirmation: '444444',
    },

    {
      id: 5,
      email: '5@5',
      family_name: '鎌田', family_name_kana: 'カマタ',
      first_name: '幸宏',   first_name_kana: 'ユキヒロ',
      post_code: '5555555',
      address: '早田県響山市大字威初2-16',
      tel: '55555555',
      is_active: true,
      password: '555555',
      password_confirmation: '555555',
    },

  ]
  )

Genre

db/seeds/.rb
Genre.create!(
  [
    {
      id: 1,
      name: '食パン',
      validity: true
    },

    {
      id: 2,
      name: '総菜パン',
      validity: true
    },

    {
      id: 3,
      name: '菓子パン',
      validity: true
    },

    {
      id: 4,
      name: '限定',
      validity: true
    },

    {
      id: 5,
      name: 'その他菓子',
      validity: true
    },
  ]
  )

Order

db/seeds/.rb
Order.create!(
  [
    {
      id: 1,
      customer_id: 1,
      addressee: '稲継亜矢子',
      post_code: '1111111',
      send_to_address: '月川県岩青山町四南寺2-15',
      how_to_pay: true,
      order_status: 0,
    },

    {
      id: 2,
      customer_id: 2,
      addressee: '鈴鹿由美子',
      post_code: '2222222',
      send_to_address: '細野県城見市世史が丘3-1-7',
      how_to_pay: true,
      order_status: 2,
    },

    {
      id: 3,
      customer_id: 3,
      addressee: '関沢智恵美',
      post_code: '3333333',
      send_to_address: '赤田県初山市十越智町6-12',
      how_to_pay: true,
      order_status: 1,
    },

    {
      id: 4,
      customer_id: 4,
      addressee: '巻譲',
      post_code: '4444444',
      send_to_address: '根野県羽島市後目台8-12',
      how_to_pay: false,
      order_status: 2,
    },

    {
      id: 5,
      customer_id: 5,
      addressee: '岡崎弥生',
      post_code: '5555555',
      send_to_address: '古岡県紫波市刈唯山3-5-2',
      how_to_pay: true,
      order_status: 0,
    },
  ]
  )

OrderItem

db/seeds/.rb
OrderItem.create!(
  [
    {
      id: 1,
      order_id: 1,
      product_id: 10,
      quantity: 4,
      order_price: 270,
      make_status: 2,
    },

    {
      id: 2,
      order_id: 1,
      product_id: 7,
      quantity: 6,
      order_price: 230,
      make_status: 1,
    },

    {
      id: 3,
      order_id: 2,
      product_id: 2,
      quantity: 2,
      order_price: 260,
      make_status: 0,
    },

    {
      id: 4,
      order_id: 3,
      product_id: 3,
      quantity: 6,
      order_price: 180,
      make_status: 1,
    },

    {
      id: 5,
      order_id: 4,
      product_id: 4,
      quantity: 8,
      order_price: 370,
      make_status: 1,
    },

    {
      id: 6,
      order_id: 5,
      product_id: 5,
      quantity: 10,
      order_price: 160,
      make_status: 2,
    },
  ]
  )

Product

db/seeds/.rb
Product.create!(
  [
    {
      id: 1,
      name: 'ブリオッシュ',
      introduction: 'バターの風味豊かな食パンです。',
      genre_id: 1,
      price: 500,
      status: true,
    },

    {
      id: 2,
      name: 'バゲット',
      introduction: '国産小麦100%のフランスパン。',
      genre_id: 1,
      price: 260,
      status: true,
    },

    {
      id: 3,
      name: 'カレーパン',
      introduction: '中辛のカレーを、甘いパン生地と組み合わせました。',
      genre_id: 2,
      price: 180,
      status: true,
    },

    {
      id: 4,
      name: 'ハンバーガー',
      introduction: '自家製のバンズに具材をたくさん挟み込みました。',
      genre_id: 2,
      price: 370,
      status: true,
    },

    {
      id: 5,
      name: 'あんぱん',
      introduction: 'どこから食べても美味しい斬新なあんぱんです。',
      genre_id: 3,
      price: 160,
      status: true,
    },

    {
      id: 6,
      name: 'クリームパン',
      introduction: '当店こだわりのカスタードをお楽しみください。',
      genre_id: 3,
      price: 170,
      status: true,
    },

    {
      id: 7,
      name: 'パン・オ・ショコラ',
      introduction: '当店の一番人気です♪',
      genre_id: 3,
      price: 230,
      status: true,
    },

    {
      id: 8,
      name: 'ラウゲン・ブロートヒェン',
      introduction: 'ドイツ風のもっちりパン。',
      genre_id: 4,
      price: 200,
      status: true,
    },


    {
      id: 9,
      name: 'いちごのお花パン',
      introduction: 'ふんわりとした生地に苺の風味を加えました。',
      genre_id: 4,
      price: 180,
      status: true,
    },

    {
      id: 10,
      name: 'モンブラン',
      introduction: 'ブリオッシュにマロンクリームを合わせてどうぞ。',
      genre_id: 5,
      price: 270,
      status: true,
    },

    {
      id: 11,
      name: 'パンケーキ',
      introduction: '強力粉からつくるしっとりしたパンケーキです。',
      genre_id: 5,
      price: 150,
      status: true,
    },
  ]
  )

データをまとめて作成

もし見た目にこだわりがなく、「文月太郎1」「文月太郎2」のようなデータで構わない場合は、複数のデータを一気に作ることもできます。

db/seeds.rb
Admin.create!(
  id: 1,
  email: 'admin@user',
  password: 'adminuser',
  password_confirmation: 'adminuser'
  )

50.times do |n|
  Customer.create!(
                   email: "1@#{n}",
                   family_name: "文月#{n}",
                   family_name_kana: "フミヅキ#{n}",
                   first_name: "太郎#{n}",
                   first_name_kana: "タロウ#{n}",
                   post_code: "1111111",
                   address: "横岡県氷川市絵向寺#{n}丁目",
                   tel: "11111111111",
                   is_active: true,
                   password: "111111",
                   password_confirmation: "111111",
                   )

  Address.create!(
                  customer_id: 1,
                  addressee: "文月なな#{n}",
                  post_code: '1111111',
                  address: '三橋県東里見町松林2-15-#{n}'
                   )

  Genre.create!(
                name: "ジャンルその#{n}",
                validity: true
                )

  Product.create!(
                  name: "自家製パン#{n}",
                  introduction: "#{n}倍美味しくなりました!(当社比)",
                  genre_id: 1,
                  price: 260,
                  status: true,

  Order.create!(
                customer_id: 1,
                addressee: "文月花子#{n}",
                post_code: "1111111",
                send_to_address: "遊明県鳥窪町",
                how_to_pay: true,
                order_status: 1,
               )

  OrderItem.create!(
                    order_id: 1,
                    product_id: 5,
                    quantity: 10,
                    order_price: 600,
                    make_status: 1,
                   )

end

後記

seedデータはいつ入れるのが適切なのか、よく分かりませんがとりあえず入れました。Modelの設定を終えた段階で一通り作ってしまうのが、一番スムーズかも知れません。データが入っていなくてもアプリ自体は作れるので、余計に迷うところです。

アプリ開発をする上で、どのような順序で進めるのが最も効率的なのか判断しきれずに、かなりめちゃくちゃな順番でやっているのではという気がしています。このECサイトを仕上げたら、解説記事たちも順番を入れ替えて、サクサク実装できるチュートリアルにまとめたいと思います。

一番の問題は「完成までこぎつけるかどうか」ですけどね! 次回へ続く!

参考

railsのseedの書き方いろいろ
Rails・seedファイルを分割して管理する

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

circleciでrspecを実行すると、Webpacker can't find applicationのエラーが出た

ローカルでは通っていたテストがcircleciで実行すると、Webpacker can't find applicationのエラーが出て、解決できたのでメモとして記録

参考URL

https://qiita.com/Shantti-Y/items/8ddc1aee3b854ee7877c

rspecのエラー全文

Failure/Error: <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

       ActionView::Template::Error:
         Webpacker can't find application in /home/circleci/FANTRA/public/packs-test/manifest.json. Possible causes:
         1. You want to set webpacker.yml value of compile to true for your environment
            unless you are using the `webpack -w` or the webpack-dev-server.
         2. webpack has not yet re-run to reflect updates.
         3. You have misconfigured Webpacker's config/webpacker.yml file.
         4. Your webpack configuration is not creating a manifest.
         Your manifest contains:
         {
         }

原因

circleciの環境の中でyarnをインストールもせずに、- run: bundle exec bin/webpackでビルドしようとしていたから、エラーが発生していた(推測)

.circleci/config.yalの中身(エラー発生時)

version: 2.1
orbs:
  ruby: circleci/ruby@0.1.2

jobs:
  build:
    docker:
      # specify the version you desire here
      - image: circleci/ruby:2.6.6-stretch-node
        environment:
          - RAILS_ENV: 'test'
      - image: circleci/mysql:8.0
        name: "db"
        command: mysqld --default-authentication-plugin=mysql_native_password
        environment:
          - MYSQL_ROOT_PASSWORD: password
      - image: selenium/standalone-chrome
        name:  "chrome"

      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/postgres:9.4

    working_directory: ~/FANTRA

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run: bundle install --jobs=4 --retry=3 --path vendor/bundle
      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      - run: bundle exec bin/webpack

# rubocop。
      # - run:
      #     name: Rubocop
      #     command: bundle exec rubocop

# rspec
      # run tests!
      - run:
          name: run tests
          command: |

            mkdir -p /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
              circleci tests split --split-by=timings)"


            bundle exec rspec \
              --format progress \
              --format RspecJunitFormatter \
              --out /tmp/test-results/rspec.xml \
              --format progress \
              $TEST_FILES

      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

解決方法

先にyarnをインストールしてから、実行してやるとエラーは消えた

 +    - run:
 +        name: yarn Install
 +        command: yarn install
      - run: bundle exec bin/webpack

反省

全般的にやけど、理解もせずに使ってしまっているからエラーの解決に苦戦してしまう。

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

Ruby 二次元配列

二次元配列とは?

2次元配列とは、プログラムで利用される配列において、配列の中に配列が入っている配列のことである。

ソースコード

fruits_price = [["apple", [200, 250, 220]], ["orange", [100, 120, 80]], ["melon", [1200, 1500]]] # 配列Key 合計額算出

fruits_price.each do |fruit|
  sum = 0
  fruit[1].each do |price|
    sum += price
  end
  puts "#{fruit[0]}の合計金額は#{sum}円です"
end

結果

appleの合計金額は670円です
orangeの合計金額は300円です
melonの合計金額は2700円です

説明

keyにフルーツの名前、valueにそのフルーツの値段が配列で複数

[["apple", [200, 250, 220]]

Ruby each文

オブジェクト.each do |変数|
  実行する処理1
  実行する処理2
end

こちらで要素を一つづつ取り出しています。

fruits_price.each do |fruit|
  sum = 0
  fruit[1].each do |price|
    sum += price
  end

ここで、配列の中の価格が入っている配列に対して要素を取り出し、
合計(sum)に格納します。

これで完成です。

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

CKEditorで余計な<ul>タグが出てくるのを防ぐ

ハマったこと

記事のコンテンツをCKEditorを使って管理しているのですが、以下のタグを保存したときに悲劇は起こりました...

<li>ほげほげ</li>
<li>ふがふが</li>
<li>なむなむ</li>

CKEditor君 :「<ul>タグなかったから追加しといたわ~ 感謝してや」

わい :「いや、テンプレート側に<ul>書いてあるんだけど余計なことしないで(# ゚Д゚)」

CKEditorの仕様で保存すると <ul> タグが消えてしまうらしい。

原因

ドキュメントを見てみると CKEDITOR.dtd.$intermediate なるものがありました。

List of elements that are not to exist standalone that must live under it's parent element.

これだ...

解決策

CKEditorの config.js に以下の設定を追加し、CKEditorの <li> に対する振る舞いを強制的に削除しました。

delete CKEDITOR.dtd.$listItem['li'];
delete CKEDITOR.dtd.$intermediate['li'];

delete CKEDITOR.dtd.$intermediate['li'];

これだけで、行けそうな気がしたんですがうまくいかなかったので $listItem を削除することでうまくいきました。

備考

私はCKEditorをGCSの画像をいい感じに扱いたいために使っていて基本的にHTMLタグを直書きなので問題ないのですが、エディタを使っている人は多分この設定を入れるだけだと、リストを挿入するときに <dt><dd> になっちゃうだけだと思うので注意が必要です(多分)

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

(初心者向け)【Rails】時短テク!slimの導入方法と使い方

はじめに

Rubyのテンプレートエンジンslimの導入方法と使い方をご説明します:relaxed:
Railsでは、html.erbというファイルでhtmlを書きますが、<h1></h1><% %>を毎回記入するのが少し面倒です。
slimを利用すると、htmlをより簡潔に書けるため、開発にかかる手間を減らすことができます。

erbとは
embedded Rubyのこと。Viewファイルの中の好きな場所でRubyのコードを実行できるようになります。

1. slim 導入方法

  • railsのディレクトリにあるgemfileに、2つのgemを追加します。
gem 'slim-rails'
# railsでslimを利用するためのgem

gem 'html2slim'
# html.erbをhtmle.slimに変換するためのgem
  • ターミナルでbundle installと打ち、gemの追加を反映させます。
bundle install
  • 以下のコマンドをターミナルに打ち、app/viewsディレクトリにある既存のhtml.erbファイルをhtml.slimに切り替えます。
bundle exec erb2slim app/views app/views
  • slimファイルが追加されたことを確認したら、既存のerbファイルは邪魔なので削除します。
bundle exec erb2slim app/views app/views -d

念の為、git commitを使ってファイルの状態を記録することをオススメします。ファイルを削除して問題が生じても、git commit前の状態に戻すことができます。

git log
#ハッシュ値を調べる
git reset --hard ハッシュ値
#出てきたハッシュ値を--hardの後に入力すると、指定したコミットまで戻ります。

2. slim 書き方

基本的なslimの使い方を列挙します。

<% %>の書き方

  • html
<% if %>
  • slim -
- if

<%= %>の書き方

  • html
<%= puts hoge %>
  • slim =
= puts hoge

テキストの書き方

  • html
<p>hoge<p>
  • slim |
p 
 | hoge

見出しタグの書き方

  • html
<h2>hogehoge</h2>
  • slim <>は必要なし
h2 hogehoge

idの書き方

  • html
<div id = id></div>
  • slim #
# id

クラスの書き方

  • html
<div class = hoge></div>
  • slim .
.hoge

コメントの書き方

  • html
<!-- hoge -->
  • slim /
/hoge

htmlとslimを見比べると、非常にシンプルに書けることが分かります。

参考記事

より詳しい情報について知りたい方は、以下のURLをご参照ください。

  • 【爆速で習得】Railsでslimを使う方法から基本文法まで

https://qiita.com/ngron/items/c03e68642c2ab77e7283

  • git commitを使って変更内容をコミットする方法【初心者向け】

https://techacademy.jp/magazine/10172

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

[Rails]カテゴリを選択するフォームを作りたい

こんにちは。
現在Railsで掲示板をつくっています。

掲示板のスレッドをカテゴリで分類して表示したかったので、スレッドをたてる時にカテゴリーを選択するようにしようと思って実装しました。

最近Railsを触り始めたので詳しい人いたらマサカリおねがいします。

バージョンとか

Ruby 2.5
Rails 5.1

DB・準備

カテゴリにたくさんのスレッドが紐づいている設計です
スクリーンショット 2020-07-09 12.08.06.png

モデル間の設定も忘れずに...

thread.rb
class Thread < ApplicationRecord
    belongs_to :category
end
category.rb
class Category < ApplicationRecord
    has_many :threads
end

まずはカテゴリを登録する

カテゴリがないと始まらないので、登録しましょう。コンソールからでもいいのですが、今後カテゴリを追加していくと考えて、登録フォームを作ってしまいます。登録したら一覧ページに飛ぶ様にしてあります。

はじめに、routes,controllerを書いていく

  • ルーティング
routes.rb
Rails.application.routes.draw do
  root 'thread#index'
  resources :thread #スレッドのルーティングも書いちゃいます
  resources :categories
end
  • コントローラ
categories.controller.rb
class CategoriesController < ApplicationController
    def new
        @category = Category.new
    end

    def create
        @category = Category.new(category_params)
        if @category.save
            redirect_to categories_path, notice: "登録しました"
        else
            render :new
        end
    end

    def index
        @categories = Category.all
    end

    private
    def category_params
        params.require(:category).permit(:name)
    end
end

  • 登録フォーム

スクリーンショット 2020-07-09 12.26.28.png

new.html.erb
<div class="col-sm-12">
    <h2 class="text-center">カテゴリの追加</h2>
    <%= form_with model: @category, local: true do |f| %>
        <div class="form_input">
            <%= f.label :name %>
            <%= f.text_field :name, class:"form-control" %>
        </div>
        <div class="form_action row">
            <%= f.submit "登録する", class: "btn col-sm-12 submit_btn" %>
        </div>
    <% end %>
</div>
  • 一覧ページ

スクリーンショット 2020-07-09 12.28.24.png

これはとりあえず表示できればいいかなと

index.rb
<div>
    <% @categories.each do |category| %>
        <%= category.name %>
    <% end %>
</div>

スレッド投稿フォームをつくる

登録ができたので、あとはスレッドの投稿フォームでカテゴリを選べる様にします。

  • コントローラ
threads_controller.rb
class ThreadsController < ApplicationController
  def new
    @thread = Thread.new
  end

  def create
    @Thread = Thread.new(board_params)
    if @thread.save
      redirect_to thread_path(@thread), notice: "投稿が完了しました"
    else
      render :new
    end
  end

  def show
    @thread = Thread.find(params[:id])
  end

  private
  def board_params
    params.require(:thread).permit(:title,:body)
  end
end

  • 投稿ページ

スクリーンショット 2020-07-09 12.36.23.png

collection_selectで選択フォームを作ることができます。

使い方としては、こんな感じに使うのですが...
collection_select(オブジェクト名, メソッド名, 要素の配列, value属性の項目, テキストの項目 [, オプション or HTML属性 or イベント属性])

今回の例だと、Category.allが「要素の配列」の部分にあたりますね。
もしかしたら、Category.allって書くよりもコントローラの方で変数に入れちゃってviewで使用。という様な形の方がいいのかもしれません。

ここはだれか指摘していただけると助かります。

threads/new.index.erb
<div class="col-sm-12">
    <h2 class="text-center">スレッド立てる</h2>
    <%= form_with model: @thread, local: true do |f| %>
        <div class="form_input">
            <%= f.label :title %>
            <%= f.text_field :title, class: "form-control" %>
        </div>
        <div class="form_input">
            <%= f.label :body %>
            <%= f.text_area :body, class: "form-control" %>
        </div>
        <div class="form_input">
            <%= f.label :category_id %>
            <%= f.collection_select :category_id, Category.all, :id, :name,
                                                            :include_blank => "カテゴリを選択してください" %>
        </div>
        <div class="form_action row">
            <%= f.submit "投稿する", class: "btn col-sm-12 submit_btn" %>
        </div>
    <% end %>
</div>

以下の様な感じで選択できるようになります。

スクリーンショット 2020-07-09 12.37.45.png

おわりに

最後までみてくれてありがとうございました。
これからたくさん記事書いて、どんどんアウトプットしていこうと思います。

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

[Rails] VScodeで.erbファイルにHTMLタグをサクサク出せるようにする

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

circleci ビルドができない、、、

ビルドすることに苦戦しすぎているので、備忘録メモ。まだ完璧に解決できていないが、一歩進んだので投稿

本題の前に一言

circleciのエラー文って不親切やし、めっちゃ難しい。dockerをちゃんと勉強しろってことなのか。
改めて現役エンジニアを尊敬する。

参考URL

https://github.com/docker-library/mysql/issues/129#issuecomment-178265632

登場ファイル

  1. .circleci/cofing.yml
  2. database.yml
  3. docker-comopose.yml

今回の抱えている問題

・MySQL、コンテナの立ち上げ失敗←今回で解決できていないが、前進した。
・コンテナの立ち上げに失敗しているからdb:createも失敗
・rubocopを実行できていない(db:createをできていないから?)
・/tmp/test-resultsがあるのに、Not Foundになってしまう←今回で解決できていない。

ファイルの中身(エラー発生時)

circleci/cofing.yml
version: 2.1
orbs:
  ruby: circleci/ruby@0.1.2

jobs:
  build:
    docker:
      # specify the version you desire here
      - image: circleci/ruby:2.6.6-stretch-node
        environment:
          - RAILS_ENV: 'test'
      - image: circleci/mysql:8.0
        name: "db"
        command: mysqld --default-authentication-plugin=mysql_native_password
        environment:
          - MYSQL_PASSWORD: "password"
          - MYSQL_ROOT_HOST: '%'

      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/postgres:9.4

    working_directory: ~/アプリ名

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run: bundle install --jobs=4 --retry=3 --path vendor/bundle
      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

# rubocop。
      - run:
          name: Rubocop
          command: bundle exec rubocop -a

# rspec
      # run tests!
      - run:
          name: run tests
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
              circleci tests split --split-by=timings)"


            bundle exec rspec \
              --format progress \
              --format RspecJunitFormatter \
              --out /tmp/test-results/rspec.xml \
              --format progress \
              $TEST_FILES

      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results
database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch("MYSQL_USERNAME", "root") %>
  password: <%= ENV.fetch("MYSQL_PASSWORD", "password") %>
  host: <%= ENV.fetch("MYSQL_HOST", "db") %>

development:
  <<: *default
  database: アプリ名_development

test:
  <<: *default
  database: アプリ名_test

production:
  <<: *default
  database: <%= ENV['DB_DATABASE'] %>
  adapter: mysql2
  encoding: utf8mb4
  charset: utf8mb4
  collation: utf8mb4_general_ci
  host: <%= ENV['DB_HOST'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>


docker-comopose.yml
version: '3'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  web:
    build: .
    command: bundle exec puma -C config/puma.rb
    environment:
      RAILS_ENV: development
    volumes:
      - .:/アプリ名
      - bundle:/usr/local/bundle
      - /app/vendor
      - /app/log
      - /app/.git
    ports:
      - "3000:3000"
    depends_on:
      - db
    tty: true
    stdin_open: true
  nginx:
    build:
      context: .
      dockerfile: ./nginx/Dockerfile
    ports:
      - '80:80'
    depends_on:
      - web
  chrome:
    image: selenium/standalone-chrome
    ports:
      - "4444:4444"
    shm_size: "2g"


volumes:
  mysql-data:
    driver: local
  bundle:
    driver: local

やったこと

開発でMySQLのコンテナの立ち上げに成功しているから、docker-comose.ymlに書いてあることを.circleci/cofing.ymlでも真似すればいけるんじゃないかと考えて、.circleci/cofing.ymlを以下のように修正

circleci/cofing.yml
version: 2.1
orbs:
  ruby: circleci/ruby@0.1.2

jobs:
  build:
    docker:
      # specify the version you desire here
      - image: circleci/ruby:2.6.6-stretch-node
        environment:
          - RAILS_ENV: 'test'
      - image: circleci/mysql:8.0
        name: "db"
        command: mysqld --default-authentication-plugin=mysql_native_password
        environment:
+         - MYSQL_ROOT_PASSWORD: password    追記
-         - MYSQL_PASSWORD: "password"    削除
-         - MYSQL_ROOT_HOST: '%'        削除

      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/postgres:9.4

    working_directory: ~/アプリ名

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run: bundle install --jobs=4 --retry=3 --path vendor/bundle
      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

# rubocop。
      - run:
          name: Rubocop
          command: bundle exec rubocop -a

# rspec
      # run tests!
      - run:
          name: run tests
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
              circleci tests split --split-by=timings)"


            bundle exec rspec \
              --format progress \
              --format RspecJunitFormatter \
              --out /tmp/test-results/rspec.xml \
              --format progress \
              $TEST_FILES

      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

結果

コンテナの作成のところで「!」マークだったのが、「ー」マークに変わった!!(ただしエラーは解決できていないっぽい。。。)「!」と「ー」の違いは何なんやろ?
それでもrubocopは実行できるようになったので、前進はできた。
スクリーンショット 2020-07-09 8.34.04.png

メモ

解決進んだら、また投稿。
/tmp/test-resultsのエラーがホンマに分からへん。デフォルト同じなはずやのに。。。

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

Mysql2::Error::ConnectionErrorを解決します

環境/バージョン

ローカルでやってます
Ruby v2.5.1
Rails v5.2.4
MySQL v8.0.19

何が起こった?

railsプロジェクトを作成したあと、`rails s'してみたらこんなエラーに遭遇

Mysql2::Error::ConnectionError (Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)):

どうやって解決した?

エラー文からみてわかる通り「あ、MySQL」かとすぐわかります。

ConnectionErrorとあるので「繋がっていませんね〜」ってすぐわかります。

database.ymlを調べて見る。

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock

  ---以下省略---

ymlファイルのpasswordが抜けてる。
この間、MySQLにpassword設定するの忘れていたからでした。

以前までは設定してなかったのでなくても接続できていましたが、設定したのもあって今回はうまく繋がってくれなかったみたいです。

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password #追記
  socket: /tmp/mysql.sock

この画面出るとホッとする。かわいい子供たち

スクリーンショット 2020-07-09 9.32.37.png

最後まで読んでくれてありがとうございました。
少してもお役に立てれば嬉しいです。

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

Rails6のAPI使用でCookieを有効にする

概要

RailsでAPIを使用するときに、セッション等でCookieを使いたくなる時があると思います。調べるとけっこう記事が出てくると思いますが、何種類か対応方法があるみたいで、今回はRails6でも上手くいった方法を書きます。

対応

【application_controller.rbの設定】

application_controller.rb
class ApplicationController < ActionController::Base
  include ActionController::Cookies

  skip_before_action :verify_authenticity_token

end

【application.rb】

  • 今回はconfig.api_only設定はtrueを前提とします。falseにするとCookieは使えるようになりますが、API利用前提とは少し外れるので。
  • ActionDispatch::CookiesActionDispatch::Session::CookieStoreを使用します。Rails の API モードでセッションを有効にするを参考にしました。私の環境だとActionDispatch::ContentSecurityPolicy::Middlewareを使わなくてもCookie使えましたが、環境によっては使う必要があるのかもです。
application.rb
# requireの設定は記載省略しています
module WebApi
  class Application < Rails::Application
    config.load_defaults 6.0

    config.api_only = true
    config.middleware.use ActionDispatch::Cookies
    config.middleware.use ActionDispatch::Session::CookieStore
  end
end

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