20200220のRubyに関する記事は25件です。

まとめ: 『オブジェクト指向設計実践ガイド-Rubyでわかる進化し続ける柔軟なアプリケーションの育て方-』(第1章-第5章)

『オブジェクト指向設計実践ガイド Rubyでわかる進化し続ける柔軟なアプリケーションの育て方』(第5章まで)要約

前置き

今回のscopeは第1章から第5章までとします。
第6章以降で扱う、継承・モジュール・コンポジション・テストは、別にまとめる方が、一つの記事としてまとまりがあると考えるためです。:bow:

気になった方は、他の記事や、原著を購入して読まれてください:pray:

ここは間違ってるんじゃない?こう理解した方がいいんじゃない?ということがありましたらご指摘お願いします:bow:

要約

第1章
オブジェクト指向とはメッセージの伝達に着目する考え。
オブジェクト指向設計とは、オブジェクト同士の依存関係を管理し、疎結合にする設計

第2章
クラスもメソッドも単一責任にする

第3章
依存を認識する。基本的には、抽象化され、安定した、自分より変更されないものに依存する

第4章
publicメソッド: クラスの主要な責務を明らかにする
privateメソッド: 変化しやすく他のクラスのオブジェクトが依存するのが危ない

第5章
ダックタイピングとは、振る舞いによって、オブジェクトの型を定義するテクニック。

本題

第1章

オブジェクト指向設計の理解の前提として、
オブジェクトオブジェクト指向の用語の理解と、設計は何故するのかを理解してからの方が
話は理解しやすいと考えました。なのでそこに軸足をおいてこの章を説明します。

  • オブジェクトとは?
    • データとメソッドにより定義される振る舞い(処理・手続き)をもつ
  • オブジェクト指向とは?
    • オブジェクト間のメッセージの伝達に着目する視点のことをいう。
    • 補足
      • 以前Kyoto.rbで、オブジェクト指向とは何か?という題材で、教えてもらった見方がわかりやすかったので載せときます。
      • 世間一般でオブジェクト指向っていう言葉がでた時の2つの視点
        1. 構成単位に着目する見方(今では割と当たり前な見方)
        2. レシーバパッシブな、メッセージに着目する見方 <-本書はここ
  • 設計がなぜ必要か?
    • 必ず行われる変更(新機能の追加etc)の際に、変更のコストを削減するために、設計は大事。
    • 現実世界の問題に当てはめると、アプリケーションが成功(ヒット)した時、設計が貧弱だと、単純であるべき変更がアプリケーション内部を伝達し、コードを破壊するから
  • オブジェクト指向設計とは?
    • オブジェクト同士の依存関係を管理し、互いを知りすぎない、疎結合にする設計
    • その結果、オブジェクトが変更を許容できるような形で依存関係を構成するための、コーディングテクニック

まとめると、

オブジェクトとは、データと振る舞いをもつもの
オブジェクト指向は、メッセージの伝達に着目する
設計は、変更をよりやりやすくするために必要
オブジェクト指向設計とは、オブジェクト同士の依存関係を管理し、疎結合にする設計のこと

第2章

オブジェクト指向はメッセージに着目するとはいえ、目立ってわかりやすいのはクラスです。
そのため、本書ではクラスの設計から説明を始めています。

  • クラスとは?
    • 似たようなオブジェクトに関する構造の設計図のこと
    • 振る舞いを定義したメソッドと、変数を定義した属性をもつ
  • 単一責任のクラスをつくる
    • 単一責任のクラスから構成されるアプリケーションは変更が簡単。
    • 反対に多くの責務をもつクラスの場合、内部がからまり、必要な振る舞いだけを取り出すことができない。
    • そう入ったクラスに依存すると、そのクラスに依存する全てのクラスを破壊する可能性がある。
    • だから単一責任のクラスで、着脱可能なユニットであることを維持する
  • クラスが単一責任かを見分けるための2つの方法
    1. クラスがもつメソッドを質問に言い換えた時、意味をなす質問になっているかどうか
      • Gearクラスさん、あなたの比を教えてくれませんか OK
      • Gearクラスさん、あなたのタイヤは何ですか ...???? NG ※ gear: 歯車。特に自転車の歯車式の変速装置
    2. 一文で、クラスを説明する
      • それと / またはが入ると、そのクラスは2つ以上の責任を負っています
      • (余談ですが、個人的にこれはすごくわかりやすくて、クラスを書く際に、コメントで、クラスの責務を一文で書くのを習慣にしてます!)
  • クラスがすることが全て、そのクラスの中心的な目的に関連していれば、そのクラスは 凝集度が高い* = 単一責任である!
  • あらゆる箇所を単一責任にする
    • メソッドも単一責任
    • 役割が何であるかを質問し、1分で責任を説明できるようにする
    • メソッドを単一責任にして、見えてきた物のうち、責任がありすぎて混沌としているクラスがあれば、それらの責任は別のクラスに分ける

まとめると、

あとからの変更を簡単にするためにも、単一責任のクラスを作る
クラスが単一かを見極めるには、「メソッドを質問に言い換える」「一文でクラスを説明する」
メソッドも単一責任にし、混沌とするクラスがあればその責務を新しいクラスに切り分ける

第3章

この章では、「依存関係を管理する」手法について説明されます。

ここでいう「依存関係の管理」とは、単一責任をもつオブジェクトが、振る舞いが実装された他のオブジェクトと、共同作業をする適切な方法のことを指します。

  • 「依存関係がある」とは、次のことをオブジェクトが知ってるとき
    • ほかのクラスの名前
    • self以外に送るメッセージの名前
    • メッセージが要求する引数
    • それら引数の順番 -> ハッシュ の利用
  • 一般的に避けた方がいい「依存」の問題
    • メッセージチェーン
    • 出発と目的地の間の、途中のオブジェクトに依存関係を作り出す
    • コードに対する依存関係
  • 依存を減らすために
    • 依存オブジェクトの注入
    • 依存を隔離
    • 引数の順番への依存を取り除く
    • 初期化の際に引数にハッシュ を使う
    • デフォルト値を指定( || or fetch)
  • 抽象化された、安定した性質をもつものへ依存することで、変更への直面を低減させる

    • 「自分より変更されないものに依存する」

まとめると、

依存関係を認識する、それぞれに依存を減らす対策をする
基本的には、抽象化され、安定した、自分より変更されないものに依存する

第4章

この章からクラスからメッセージへと話の重点が移ります。
メッセージとは、メソッドで呼び出し(call)して、オブジェクトへ伝達するもののことです。
またインターフェースとは、クラスの主要な責務を明らかにするpublic(公開情報)メソッドのことです。

  • オブジェクト指向アプリケーションは、クラスから成り立つが、メッセージにより定義される
  • ここでいうインターフェースとは、クラス内にある、外部に晒されているメソッド(パブリックメソッド)のこと
  • クラス内部で、public(公開情報)であるメソッドと、private(非公開情報)であるメソッドとを分ける

    • publicメソッド
      • 安全に依存でき、クラスの主要な責任を明らかにする
    • privateメソッド
      • 変化しうる部分で、他のクラスのオブジェクトが依存するのは危険。テストで言及されないことも。
      • (余談ですが、第9章で、privateメソッドをテストしない理由を、不安定で、他のオブジェクトによる利用を避けるためと説明があり、ハッとしました!!!!)
  • メッセージに基づく設計へ視点を変更し、このメッセージを送る必要があるけど、誰が応答すべきかと思考を変える

    • シーケンス図を書くことにより、パブリックインターフェースとして公開すべきメソッドを顕にし、メッセージを明らかにすることができる
  • publicインターフェースでは、「何を」に着目し、依頼先のクラスの責務として「どのように」(方法)を任せる

    • 「私は自分が何を望んでいるか知っているし、あなたがあなたの担当部分をやってくれると信じているよ」が理想

まとめると、

オブジェクト指向アプリケーションはメッセージにより定義される
クラス内部で、クラスの主要な責務を明らかにするpublicメソッドと変化しやすく他のクラスのオブジェクトが依存するのが危ないprivateメソッドを分ける
「どのように」については、依頼先クラスを信頼し、そのクラスの責務として任せる

第5章

はじめに
ダックタイピングの背景には、
メッセージを設計の中心におくパブリックインターフェースを構築するという2つの考えがあります

  • ダックタイピングとは
    • オブジェクトを、クラスではなく、メッセージ(振る舞い)によって定義するテクニック
    • オブジェクトを特定のクラスから切り離し、何を「する」かで、オブジェクトの仮想の型を定義する
  • ポリモーフィズムとは
    • 相手がどのクラスのオブジェクトかを意識せずに、メッセージを送れる仕組み
    • 同じ名前のメソッドを複数のクラスで使用できるようにし、そのメソッドを通して、暗黙的に複数のインスタンスの動作を切り替えれるようにすること
  • ダックタイピングを認識する3つの
    • クラスで分岐するcase文
    • kind_of?とis_a?
    • responds_to?
    • ダックタイピングという言葉に囚われすぎずに、これらが出てきた時に、ダックタイピングができないかどうか考えてみるくらいの温度感からまず、接してみるといいのかも。

まとめると、

ダックタイピングとは、振る舞いによって、オブジェクトの型を定義するテクニック。
ダックタイプを見極める3条件を見つけると、まず疑ってみる。

読後感想

オブジェクト指向設計の考えが深まった。
ただ、第5章のダックタイピングの章が、何を目的として、独立した章を用意しているのかが僕の理解では追いつきませんでした:bow:
Rubyでは、型≠クラスで、振る舞いによって型が規定されることを示すため??:thinking:
時間を置いて、読み返してみよう。

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

【rails】bundle install したときに vender/bundle にインストールされるのをデフォルトに戻す

はじめに

Railsで開発している時、エラーを解決しようとガチャガチャやった後、bundle installしたらなぜかプロジェクト下の vender/bundle に gem がインストールされるようになってしまいました。とりあえず前の状態に戻したいと思い、原因と解決法を探りました。

bundle install --path vender/bundle

調べてみると、むしろbundle install --path vender/bundleを推奨する記事が散見されましたが、下記の記事のようにこの慣習に疑問を呈している記事もありました。

bundle install時に--path vendor/bundleを付ける必要性は本当にあるのか、もう一度よく考えてみよう

デフォルトに戻す

ググると以下のような方法をすぐに見つけました。

Bundlerがどこからgemを探すかは、プロジェクトルートの下の.bundle/configに設定ファイルがあります。これを消すことで、新たな場所にbundle installできるようになります。

https://teratail.com/questions/16066

しかし、私の場合この方法では症状解決しませんでした。
そこで、もう一度bundle installしプロジェクト下に作成された .bundle/config ファイルを確認しました。

---
BUNDLE_WITHOUT: "production"

…特にpathオプションに関連するものはありませんでした。

そこでbundler configでどのようなオプションが設定されているかを確認しました。

$ bundler config
Settings are listed in order of priority. The top value will be used.
without
Settings are listed in order of priority. The top value will be used.
path
Set for the current user (/Users/punkshiraishi/.bundle/config): "vendor/bundle"

これで path オプションが /Users/[ユーザ名]/.bundle/config で設定されていることが分かったので開いてみると

/Users/[ユーザ名]/.bundle/config
---
BUNDLE_WITHOUT: "production"
BUNDLE_PATH: vendor/bundle

ありました!
このBUNDLE_PATH: vendor/bundleを削除し、bundle installすることで、使用中の Ruby 直下に gem がインストールされるようになりました!

更に調べてみると bundler のオプションは

  • Railsプロジェクト下の .bundle/config
  • 環境変数
  • ユーザのホームディレクトリ下の .bundle/config

の3つにあるということが分かりました。プロジェクト直下の .bundle/config を削除しても設定が戻らないという方は上記も調べてみると良いかもしれません。

参考

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

複数テーブルに存在するカラムのi18n定義を共通化する

複数テーブルに共通して存在するカラム(ex. created_at, updated_at)を各テーブルで定義するのは冗長でめんどくさい。

config/locales/translation_ja.yml
ja:
  activerecord:
    attributes:
      user:
        name: ユーザ名
        created_at: 登録日時
        updated_at: 更新日時
      event:
        name: イベント名
        created_at: 登録日時
        updated_at: 更新日時

こんな風に書かなくても、attributes直下で定義すれば使い回すことができる。

config/locales/translation_ja.yml
ja:
  attributes:
    created_at: 登録日時
    updated_at: 更新日時
<%= l(User.created_at) %>

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

Railsで投稿した内容をGoogleMap上にmarkerで表示させる方法

はじめに

世界中の絶景を投稿できるアプリが作れます。
マップの表示方法はGoogleMapsAPIを使いました。
投稿した内容から自動的に軽度と緯度を取得してGoogleMapsAPIのマップ上に表示させます。
完成形はこんな感じ
スクリーンショット 2020-02-20 19.32.20.png

環境

  • Ruby 2.5.1
  • Rails 5.2.3
  • MySQL 5.6

目次

  • GoogleMapsAPIのKEYを取得
  • 簡単なアプリを作成しとりあえずGoogleMapsをアプリ上に表示させる
  • 投稿機能を作り軽度、緯度取得し、マップ上に表示

GoogleMapsのKEYを取得

(グーグルアカウントにログインしてから始めてください)
まずGoogle Map PlatformからKEYを取得します
スクリーンショット 2020-02-20 15.25.11.png

使ってみるを選択。
スクリーンショット 2020-02-20 15.28.16.png

全て選択し続行。
プロジェクト名はなんでもいいですが今回はMyProjectを選択
支払い方法を入力します。
topページに移動しますので下の方のMaps Platfomを選択
スクリーンショット 2020-02-20 15.40.51.png

Maps JavaScript APIを選択し有効を押します

スクリーンショット 2020-02-20 15.42.15.png

スクリーンショット 2020-02-20 15.47.16.png

画面上にあるAPI とサービスの認証情報

スクリーンショット 2020-02-20 15.48.21.png

認証情報を作成を選択しAPIを作成。
画面にでてくるのがAPIキーになるので保存しといてください。(必要な場合はキーの制限をしてください)

簡単なアプリを作成しgooglemapをアプリ上に表示させる

アプリ名はここではgmapにします
ここの部分は自分のアプリ名にしてください。
まずアプリの雛形とデータベース設計します

ターミナル上で

rails _5.2.3_ new gmap -d mysql
cd gmap
rails g controller maps
rails db:create
rails db:migrate

erbになっているのでhamlに変換します。
ここは任意で大丈夫です。
erbをhamlに簡単に変えてくれるサイトです

Gemfile
gem "haml-rails", ">= 1.0", '<= 2.0.1'
bundel install
rails haml:erb2haml

マップを表示させます
rails5.2以上なので今回はcredentialsを使います

routes.rb
Rails.application.routes.draw do
  root to: "maps#index"
end
views/maps/index.html.haml
%div{:style => "width: 100%;"}
  #map{:style => "width: 100%; height: 100vh;"}


:javascript
  function initMap(){
    let map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: -34.397, lng: 150.644},
    zoom: 8
    });
  }

%script{:src => "https://maps.googleapis.com/maps/api/js?key=#{Rails.application.credentials[:GOOGLE_MAP_KEY]}&callback=initMap"}

先ほどのAPIキーをcredentialsで隠しておきます
ターミナル上で

EDITOR="vi" bin/rails credentials:edit
GOOGLE_MAP_KEY: MyAPIKey

MyAPIKeyの部分は先ほど取得したkeyを入力してください
これで一応マップ表示ができたと思います!
スクリーンショット 2020-02-20 18.00.34.png

投稿機能を作り軽度、緯度取得

まずは投稿機能を作るためにpostsのviewとcontroller,modelを作ります。

rails g scaffold posts name:string description:string latitude:float longitude:float
rake db:migrate

表示をgmaps4railsのgemを使って表示する方法に変えます。

Gemfile
gem 'gmaps4rails'

underscore、gmaps/googleをapplicaton.jsに追加します。

//= require underscore  //追加
//= require gmaps/google  //追加
//= require_tree .

app/assets/javascripts/underscore.jsを作り
こちらの中身を全部コピペ

views/maps/index.html.haml
%div{:style => "width: 100%;"}
  #map{:style => "width: 100%; height: 100vh;"}

:javascript
  handler = Gmaps.build('Google');
  handler.buildMap({ 
    provider: {mapTypeId: 'hybrid'},
    internal: {id: 'map'}
    }, 
    function(){
    markers = handler.addMarkers(#{raw @hash.to_json})
    handler.bounds.extendWith(markers);
    handler.fitMapToBounds();
    handler.getMap().setCenter(new google.maps.LatLng(35.681298, 139.7640582));
    handler.getMap().setZoom(4);
  });

provider: {mapTypeId: 'hybrid'}の部分でマップのタイプを変えることができます。
何もなければ最初のタイプです。
他のタイプは下記を参照してください。

 ROADMAP 道路や建物などが表示される地図です
 SATELLITE 衛星写真を使った地図です
 HYBRID ROADMAPとSATELLITEの複合した地図です
 TERRAIN 地形情報を使った地図です

applicationにkeyを移してどこでも使えるようにします。

application.html.haml
!!!
%html
  %head
    %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
    %title Gmap
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
    %script{:src => "//maps.google.com/maps/api/js?key=#{Rails.application.credentials[:GOOGLE_MAP_KEY]}"}
    %script{:src => "//cdn.rawgit.com/mahnunchik/markerclustererplus/master/dist/markerclusterer.min.js"}

    = yield

コントローラーに投稿した場所にmarkerが立つようにします。

maps_controller.rb
class MapsController < ApplicationController
  def index
    @posts = Post.all
    @hash = Gmaps4rails.build_markers(@post) do |post, marker|
      marker.lat post.latitude
      marker.lng post.longitude
      marker.infowindow post.description
    end
  end
end

marker.infowindow post.descriptionのところに表示させたいカラムを変えれば表示内容を変えれます。
今回はdescriptionにします。

これでマーカーが立つようになりました。
あとは新規投稿で軽度、緯度を取得できるようにします

gem 'geocoder'
post.rb
class Post < ApplicationRecord
  geocoded_by :name
  after_validation :geocode
end

名前のカラム名で軽度と緯度が取得できるようになったので
あとは送信する時に余計なカラムを消しておきます。

posts/_form.html.haml
  .field
    = f.label :name
    = f.text_field :name
  .field
    = f.label :description
    = f.text_field :description
  .field
    = f.label :latitude
    = f.text_field :latitude
  .field
    = f.label :longitude
    = f.text_field :longitude
  .actions
    = f.submit 'Save'

のlatitudeとlongitudeの項目を消します。
これで完成です!

最後に

間違っているところがあったら教えてください…

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

RailsアプリをDockerで作ってCircleCIで自動テストしてHerokuにデプロイした話

はじめに

どうも、Pirikaraです。
久しぶりの投稿となりました。

今回は、Docker環境でRailsアプリケーションを開発し、
CircleCIで自動テスト、Herokuにデプロイするところまでやっていきたいと思います。
個人開発でやってみましたが、このエラーの山々......
スクリーンショット 2020-02-20 15.00.43.png

一つ一つエラーを解決して設定ファイルを修正してを繰り返し繰り返し......
やっとまともに動くようになりました。
スクリーンショット 2020-02-20 15.03.12.png

初めてやるよって方は僕の屍を踏み越えていってください。
ちなみに間違ってるよーとか改善点とかご指摘いただけると幸いです。

環境

・Mac OS
・Ruby 2.5.3
・Rails 5.2.2
・MySQL 5.7

また、Dockerがインストールされていることを前提としています(Dockerコマンドが使用できる状態)。
インストールしていない場合は、公式サイトからアカウントを作ってログインし、DockerHubからダウンロード・インストールします。
Dockerhub

Herokuについても登録していない場合は登録の必要があります。
公式サイトから登録が可能です。
Heroku

アプリの構成

スクリーンショット 2020-02-20 13.02.15.png

開発環境からDockerを導入し、RailsとMySQLのimageでコンテナを作りました。苦労していた環境構築がすぐに出来て感動。
GithubにpushするとCircleCIによる自動テストが行われて、テストをパスするとHerokuに自動でデプロイされる仕様です。
また、Herokuでは有料であればMySQLが使用できるみたいですが、僕はお金がないので本番環境のみPostgreSQLを使用します。

では、
1. Docker開発環境の構築
2. CircleCIの導入と自動テスト
3. Herokuに自動デプロイ
の順番でやっていきましょう。

1.Docker開発環境の構築

まず、アプリケーションのディレクトリを作成します。
ここではsample_appとします。

sample_appディレクトリを作成
$ mkdir sample_app

sample_appディレクトリに移動
$ cd sample_app

ディレクトリ内に「Dockerfile」「docker-compose.yml」「Gemfile」「Gemfile.lock」の4ファイルを作成します。

$ touch Dockerfile
$ touch docker-compose.yml
$ touch Gemfile
$ touch Gemfile.lock

今ディレクトリはこんな感じです。
スクリーンショット 2020-02-20 14.16.06.png

では、それぞれ中身を書いていきましょう。(Gemfile.lockは空のままです)

Dockerfile
FROM ruby:2.5.3

#必要なパッケージのインストール
RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs           
#作業ディレクトリの作成
RUN mkdir /bbs_app

#作業ディレクトリをAPP_ROOTに割り当てる
ENV APP_ROOT /bbs_app 
WORKDIR $APP_ROOT

#ローカルのGemfileを追加
ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

#Gemfileのbundle installを実行
RUN bundle install
ADD . $APP_ROOT
docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "4306:3306"

  web:
    build: .
    command: rails s -p 3000 -b '0.0.0.0'
    environment:
      RAILS_ENV: development
    volumes:
      - .:/bbs_app
    ports:
      - "3000:3000"
    links:
      - db
Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.2'

ところどころ違いますが、
設定の内容についてはこちらの記事が詳しくて参考になったので、任せます。
DockerでRuby on Railsの環境構築を行うためのステップ【Rails 6対応】

ここまで書けたら、dockerコマンドでrails newします。

$ docker-compose run web rails new . --force --database=mysql --skip-bundle

あとでimageを構築する際にDockerfileにしたがってbundle installが実行されるので、ここではスキップします。

作成されたconfig/database.ymlを編集します。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # docker-compose.ymlのMYSQL_ROOT_PASSWORD
  host: db # docker-compose.ymlのservice名

dockerを起動します。

imageを構築(コンテナは作成しない)
$ docker-compose build

コンテナを構築・起動
$ docker-compose up

データベースを作成します。

$ docker-compose run --rm web rails db:create

--rmオプションをつけることで、コンテナが実行されたあと削除されます。
「rails g controller ~」や「rails console」など、docker-composeから始まるコマンドで実行していくことになりますが、
--rmをつけていないと実行するたびにコンテナが増えていってしまうので、都度削除しています。僕は。

ここまでできると、localhost:3000へアクセスしてサーバーの起動を確認することができます。おめでとう。
スクリーンショット 2020-02-20 14.42.11.png

2.CircleCIの導入と自動テスト

こちらのブログをパク・・・参考にしました。
導入まで大変わかりやすく、参考になりました。こちらを参考に導入してみてください。

ウェブ系ウシジマくんのテックブログ

「.circleci」というディレクトリを作成し、これ以下に「config.yml」というファイルを作成します。
また、configディレクトリ以下に「database.yml.ci」というファイルを作成します。

こんな感じ。
スクリーンショット 2020-02-20 14.56.46.png

では、作成したファイルの中身を書いていきましょう。

config.yml
version: 2
jobs:
  build:
    docker:
    - image: circleci/ruby:2.5.3-node-browsers
      environment:
        - BUNDLER_VERSION: 2.0.2
        - RAILS_ENV: 'test'
    - image: circleci/mysql:5.7
      environment:
        - MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
        - MYSQL_ROOT_HOST: '127.0.0.1'

    working_directory: ~/bbs_app


    steps:
    - checkout

    - restore_cache:
        keys:
        - v1-dependencies-{{ checksum "Gemfile.lock" }}
        - v1-dependencies-

    - run:
        name: install dependencies
        command: |
          gem install bundler -v 2.0.2
          bundle install --jobs=4 --retry=3 --path vendor/bundle

    - save_cache:
        paths:
        - ./vendor/bundle
        key: v1-dependencies-{{ checksum "Gemfile.lock" }}

    # Database setup
    - run: mv ./config/database.yml.ci ./config/database.yml

    # Database setup
    - run:
        name: Databasesetup
        command: |
           bundle exec rake db:create
           bundle exec rake db:schema:load

    # run tests!
    - run:
        name: Run rspec
        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.ci
test:
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: 'root'
  port: 3306
  host: '127.0.0.1'
  database: app_test #database.ymlのテスト環境のデータベース名を参照

これでGithubへpushすると、自動でテストが実行されるようになります。
プルリクエストを作ってmasterブランチへmergeしていくと思うのですが、テストに失敗するとmergeすることができません。
スクリーンショット 2020-02-20 19.50.23.png
ほらね。

Detailのリンクからエラー箇所も教えて頂けます。
スクリーンショット 2020-02-20 19.54.06.png

テストをパスすると、Mergeボタンがアクティブになります。
スクリーンショット 2020-02-20 20.00.18.png

※ seeds.rbを読み込みたい場合

.circleci/config.ymlでseeds.rbの読み込み設定をしたところ、うまくいかなかったのでspec_helperにseeds.rbを読み込む設定をしました。

「database_cleaner」というgemを導入し、spec_helperへ処理を書いていきます。
これで、Rspec実行の際にデータベースがリフレッシュされ、seeds.fileが読み込まれます。

Githubへpushした際に実行された場合は大丈夫なのですが、ローカルでdocker-composeコマンドからrspecを実行した場合にデータベースがリセットされてしまうので注意してください。

Gemfile
group :development, :test do
  # rspec実行時にDBをリセットする
  gem 'database_cleaner'
end
spec_helper.rb
# テスト実行時にDatabaseをリセットし、seeds.rbを読み込む
RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
    Rails.application.load_seed
  end
end

3.Herokuに自動デプロイ

Herokuにデプロイするため、ファイルを修正・追加します。

まずはGemfile。
開発環境ではMySQL、本番環境ではPostgeSQLを使用するので、「pg」のgemをインストールします。

Gemfile
# 変更前
gem 'mysql2', '>= 0.4.4', '< 0.6.0'

# 変更後
gem 'mysql2', '>= 0.4.4', '< 0.6.0', groups: %w(test development), require: false
gem 'pg', '~> 0.19.0', group: :production, require: false

次に、database.ymlを編集していきます。
本番環境でPostgreSQLを使う設定をします。

database.yml
production:
  <<: *default
  adapter: postgresql
  encoding: unicode
  pool: 5

そして、「heroku.yml」というファイルを新たに作成します。
作成場所はアプリ直下です。(GemfileとかDockerfileと一緒)
今回はDockerコンテナを使用して開発をしているので、コンテナごとHerokuのサーバーに持っていきます。
そのための設定ファイルです。知らんけど。

DockerコンテナをHerokuにアップする方法は公式サイトを参考にしました。

Dockerを使用したデプロイ

heroku.yml
build:
  docker:
    web: Dockerfile
run:
  web: bundle exec puma -C config/puma.rb

アプリケーション側の設定はこれで完了です。

ではHeroku側の設定をしてデプロイしていきましょう。
ターミナルから、Herokuにログインします。

$ heroku login

# ログインするかどうか確認されます。
# クリックするとブラウザが立ち上がり、Herokuのログイン画面になります。ログインしてください。
heroku: Press any key to open up the browser to login or q to exit: 

# ログインが完了すると、ターミナルに「ログインしたよー」って表示されます。
Opening browser to https://cli-auth.heroku.com/auth/browser/**************
Logging in... done
Logged in as *******@email.com

次に、Herokuにアプリケーションを作成します。

# アプリケーション名のところには好きな名前を入れてください。それでURLが作成されます。
$ heroku create アプリケーション名

Creating app... done, ⬢ *******
https://******.herokuapp.com/ | https://git.heroku.com/*******.git

ちなみにこの時、Herokuのリポジトリが作成されています。
もしアプリケーションの名前を変更したいときは、Herokuの管理画面から名前を変更することに加えて、リポジトリを削除する必要があります。

$ git remote rm heroku

このコマンドで消せます。
消さないとアプリ名とリポジトリ名が異なるためにデプロイできなくなります。

そして、Heroku側でPostgreSQLを使用する設定をします。

$ heroku addons:create heroku-postgresql:hobby-dev

# PostgreSQLのデータベース作ったよーって通知がきます。
Creating heroku-postgresql:hobby-dev on ⬢ ******... free
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pg:copy
Created postgresql-transparent-70433 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation

今回DockerコンテナをHerokuにのせるので、Heroku側でコンテナにアプリのStackをセットします。

$ heroku stack:set container

この辺の話は「heroku.yml」の設定の時に出てきた公式サイトに全部載ってます。
Dockerを使用したデプロイ

これでデプロイするための設定は完了しました。
masterブランチにアプリをpushしたのち、

$ git push heroku master

このコマンドでHerokuのリポジトリにpushすることでデプロイが完了します。エラーが起こらなければ。

さて、ついに自動デプロイです。
circleciを通してHerokuにデプロイしていくので、config.ymlファイルにdeployの処理を追加します。

config.yml
# 省略
    - deploy:
        name: Deploy Master to Heroku
        command: |
          if [ "${CIRCLE_BRANCH}" == "master" ]; then
            git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git master
          fi

「HEROKU_API_KEY」と「HEROKU_APP_NAME」については、CircleCIに設定した環境変数が読まれるため、
こちらを設定していきます。

どちらもHerokuの管理画面から取得できます。

アカウント設定からHEROKU_API_KEYに当たるAPI KEYを......
スクリーンショット 2020-02-20 18.47.58.png

アプリケーションの設定からHEROKU_APP_NAMEを......
スクリーンショット 2020-02-20 19.28.44.png
それぞれ取得します。

CircleCIのアプリケーション管理画面から、右上の「Project Setting」をクリックします。
スクリーンショット 2020-02-20 19.30.15.png

Environment Variablesから「Add Variables」をクリックして、先ほど取得した値を環境変数として設定します。
これがconfig.ymlで使用されます。
スクリーンショット 2020-02-20 19.33.39.png

設定は以上です。
これで、

Githubへpush → CircleCiが自動でテスト → パスすればHerokuに自動でデプロイ

という風に動きます。良かったね。

終わりに

DockerとCircleCIを利用したのは初めてでしたが、
環境構築もテスト・デプロイも簡潔になったので、非常に便利でした。

特にテストコマンドやデプロイコマンドについては「コマンド打つだけやしなぁ......」と自動化の恩恵を甘く見ていましたが、
チリも積もればなんとやらです。
いちいちコマンドを打つ煩わしさから解放され、アプリの開発スピードも段違いだったように感じます。

参考にしていただけたら幸いです。

またね。

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

【初心者向け】form_withでundefined method `form_with' forというエラーが出た際の対応方法について

Railsでform_withを使用した際、undefined method `form_with' for やDid you mean? form_tagと表示されてしまって詰まった話

Railsでアプリケーションを作成中、投稿フォームの実装時、form_withを使用したところ、永遠とエラーが出てしまって解決に時間がかかってしまったため、備忘録として残す。

結論、form_withはRailsのバージョンが5.1 以上じゃないと動かないようです。

Railsガイド様いつも参考にしています
https://railsguides.jp/5_1_release_notes.html

Webcamp様にも記載ございました
https://web-camp.io/magazine/archives/17665

何をすればいいのか?

Gemfileの中にあるRailsのgemのバージョンをアップグレードする

Railsバージョンアップデートの手順

  1. gemファイルのrailsを5.1以上で指定する(ここでは5.2.4を指定しています)
gem 'rails', '~> 5.2.4'
  1. bundle updateする(※注意:bundle installではない!!!!)
bundle update

Railsをバージョンアップした際にRails sで立ち上がらない問題が発生

undefined method `halt_callback_chains_on_return_false=' for ActiveSupport:Module (NoMethodError)

これが表示されたら、configファイルの中身の以下

config/initializers/new_framework_defaults.rb

以下をコメントアウト

ActiveSupport.halt_callback_chains_on_return_false = false
と
Rails.application.config.action_controller.raise_on_unfiltered_parameters = true

上記をコメントアウトし、これで通常通りRails sで起動した。
(よかった・・・・でも勉強になった)

ちなみに、ActiveSupport.halt_callback_chains_on_return_falseの意味はよくわかりません・・・。
https://techracho.bpsinc.jp/hachi8833/2017_02_21/35843

参考記事
https://qiita.com/luglio22/items/0dd520f3748935bada81

もし
ここは違う、ここはこうした方が良い等々ございましたらご指摘いただけますと幸いです。
最後までみていただき、ありがとうございます。

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

rails s 起動しない 解決方法の1つ

チーム開発においてエラーが発生!

※ 初心者向けに何か助けになればと思い記述しております。
※ アウトプットの練習も兼ねております。

開発環境 rails 5.2.4.1
ruby 2.5.1
クラウドサービスはAWSを使用
現状自動デプロイまで完了

5名でフリマアプリの作成

githubアプリより最新のタスクデータを取得のため
masterブランチにブランチを切り替えpullを実行

rails sでサーバーを立ち上げた際に起動しない

.....
=> Rails 5.2.4.1 application starting in development 
=> Run `rails server -h` for more startup options
Exiting
Traceback (most recent call last):
    85: from bin/rails:3:in `<main>
.....

試した事
PC再起動
bunde linstall
rails db:migrate
rails db:rollback
rails db:drop
rails db:migration:reset
rake db:migrate:reset 等...いずれもエラー表示

冷静にエラーの記述を見るとrails sの起動時に下記の内容が

 config/initializers/carrierwave.rb:10:in `block in <top (required)>'

指定のファイルconfig/initialize/carriewave.rbの記述に注目

 provider: 'AWS',
 aws_access_key_id: Rails.application.credentials[:aws] 
 [:access_key_id],
 aws_secret_access_key: Rails.application.credentials[:aws] 
 [:secret_access_key],
 region: 'ap-northeast-1'

結果awsのマスターkeyをpullでは取得できていないためであったと思われる。
configファイルの参加にmaster.keyのファイルを作成しマスターよりkeyを一旦取得し記述するとエラーは解決。

しかしこのままではセキュリティに問題がある為、別の方法を模索中です。

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

処理速度が早いrender

renderは使い方を間違えると表示に読み込みが遅くなってしまう。
今回はパフォーマンスを意識して学習した。

NG:each doで部分テンプレート読み込み

each doは繰り返し処理なので、部分テンプレートもなんども読み込みする。

悪い例
# eachを使う処理はNG
<% @products.each do |product| %>
  // 3つの書き方を記述しているが、each doを利用しているのでだめ
  <%= render product %>
  <%= render 'products/product', product: product %>
  <%= render partial: 'products/product', locals: { product: product } %>
<% end %>

@productsが1000個の場合、1000回部分テンプレートを読み込む事になる。

処理が早い書き方

正解
= render partial: 'product', collection: @products, as: "product"

 # partial: 'product'    > _product.html.erbを読み込む


 # collection: @products > 複数あるproductを1つずつ処理する。 > each doの代わりになる。


 #  as: "product" > @products の単体の変数 、部分テンプレートで利用する変数はproductになる。

これだと一度しか部分テンプレートが読み込まれないから、処理速度が早まる。

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

Jenkins Pipelineのpipeline {...}の"pipeline"と"{...}"は何か(Groovy初心者向け、他言語経験者向け)

背景

お仕事でJenkins Pipelineを書くことになりました。早速サンプルをコピーし、Hello Worldが表示されてうれしい一方、こんな疑問が。

この、pipeline{...}とは何なのか?どういう文法規則なのか?

Jenkinsfile
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'echo "Hello World"'
            }
        }
    }
}

Jenkinsfile自体は単なるデータ構造(JSONやYAMLの仲間)に見えます。ということは何か規則があるはずです。しかしそれが書かれているページが見つからない。
職場のつよつよエンジニアに聞いたところ、JenkinsfileはGroovyのプログラムということが分かりました。

その後自分で調べたことと、他の言語との対比を書いておきます。

"pipeline"と"{...}"は何かの答え

pipelineメソッド名
{...}クロージャ

pipeline {...}は、pipelineメソッドに、{...}というクロージャを引数として渡す、という構文です。

pipelineはメソッドなので、Jenkinsfileの実行時に

def pipeline(Closure c) {
    // 前処理
    // クロージャを呼び出す
    c();
    // 後処理
}

のように定義されたメソッドが(どこかから)読み込まれています。

もっと詳しく

groovyのメソッド呼び出し

上記のpipelineの呼び出しを冗長に書くと、

pipeline({ ->
    // 処理
})

です。
groovyのクロージャは{x, y -> x + y}のように、{引数 -> 処理}のように書きます。引数がない場合は、引数 ->の部分は省略可能です1。引数を省略すると、

pipeline({
    // 処理
})

の形になります。
また、メソッド呼び出しのカッコは、引数がある場合は省略可能なので、

pipeline {
    // 処理
}

になります。Jenkinsfileと同じ形になりました。

クロージャ

クロージャとは、メソッドの呼び出し側で動いているような振る舞いをする無名関数です。
こちらの説明が分かりやすいです。
クロージャ - Apache Groovyチュートリアル

他の言語で書いてみると

Groovyのクロージャを他の言語と対比します。(ここに書ききれない言語につきましてはコメントで補足お願いします)

Ruby

Groovyの

pipeline {
    // 処理
}

という記法は、Rubyの

pipeline {
  # 処理
}

という記法と対応します。見た目は同じですね。GroovyはRubyに強い影響を受けていることがわかります。
Rubyのブロックを、Groovyではクロージャと言います。ブロックとクロージャの違いは、こちらのページが分かりやすいです。
Rubyist のための他言語探訪 【第 5 回】 Groovy

JavaScript

Groovyの

pipeline {
    // 処理
}

という記法は、JavaScriptの

pipeline(() => {
    // 処理
})

という記法に対応します。JavaScriptではメソッド呼び出しのカッコは省略できず、引数がない場合も引数を囲むカッコを省略できないですが、Groovyでは省略が可能です。

Java

Groovyの

pipeline {
    // 処理
}

という記法は、Javaの

pipeline(() -> {
    // 処理
})

に対応します。JavaScriptと同様Javaでもメソッド呼び出しのカッコと、引数がない場合は引数を囲むカッコを省略できません。Groovyではこの2つは省略可能です。

Python

Groovyの

pipeline {
    // 処理
}

という記法は、Pythonで対応する記法がないのですが、あえて書くとしたら

pipeline(lambda: 処理)

です。Pythonの場合、lambda式は複数行にまたがることができませんが、Groovyでは可能です。

参考資料

あとがき

Jenkinsfileを作成している最中、「コピペで動いてはいる、ただ、なんで動いているのか分からない」という状態でしたが、Groovyのプログラムということを理解し、実態がつかめました。
Jenkins Pipelineの記事がまだまだ少ないので、分かったことを今後も書けたらと思います。


  1. 正確に言うと、{-> 処理}{ 処理 }では挙動が変わります。 

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

RubyOnRails消化のコツ

はじめに

TECH::EXPERTというプログラミング教室に通っている添野です。
今日はRails苦戦中の方におすすめの記事です。

Railsはプログラミング初心者が一見すると難解ですよね。
今回は、Rails初心者だった私が理解をするために用いたコツを紹介します。

Rails消化のコツ

結論をいいます。
Railsのコードを「決まっているもの」と、「自分で決めていい/または決めたもの」に分類してください。これだけです。逆に言うと、これ以外に「理解する方法」はありません。理由は「理解=分類」だからです。「例え話が分かりやすい理由」とかも「理解=分類=比較=例え話」だからですね。

少し話がそれましたが、以下、消化のコツを解説していきます。

次のコードは、Railsコントローラの記述の一部です。

@tweet = Tweet.new

決まっているもの

「.new」は決まっているものです。
「決まっているもの」とは、Railsというカレールーに予め入っているスパイスです。スパイスはもうとっくの昔にカレールーの中に溶け込んでおり、それを後からスパイスだけ取り出して変えるということですので、錬金術師以外にはできません。語尾に草を追加して「.neww」とか書くと、エラーになります。

自分で決めていい/または決めたもの

「Tweet」は決めたもの、「@ tweet」は決めていいものです。

「Tweet」はテーブルの名前で、データベース作りをするときにあなたが既に決めています。もう決めたものなので、1文字でも打ち間違えるとエラーです。もう皿に盛ってしまったライスを一旦捨てて、別のライスを盛るというのは、超美食家パーソナルコンピューター様は決して許しません。エラーという名の罵詈雑言を浴びせられ、基本的には食べてくれません。

@ tweetというのは、これからカレーに入れる具材の名前です。この時点で決めることができます。ここだけ唯一「@ tweet」以外で書いてもエラーにはなりません。


以上、こんな感じでひたすら分類を繰り返してください。

まとめ

とにかく、上記の2つを分類することを意識しつつコーディングを進めると良いかな、と私は思います。美食家をうならせるようなカレー作りを、明日もがんばっていきましょう!

おすすめ記事
「TECH::EXPERTはカレーづくり教室だった話」

添野又吉(@enjoy_omame)
https://twitter.com/enjoy_omame

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

#Stripe API checkout / Web form input credit card / setup mode / #Ruby example

  • publish checkout session by API
  • use checkout session id in client e.g html + js and redirect to Stripe webform
  • After checkout you can retrieve payment method by API

https://stripe.com/docs/payments/checkout/collecting

# Docs

# https://stripe.com/docs/payments/checkout/collecting

# Code

require 'stripe'

Stripe::api_key = ENV['STRIPE_SECRET_KEY']

checkout_session = Stripe::Checkout::Session.create(
  payment_method_types: ["card"],
  mode: "setup",
  # customer_email: "",
  # client_reference_id: "",
  success_url: "http://example.com",
  cancel_url: "http://example.com",
)

# customer = Stripe::Customer.create
#
# checkout_session = Stripe::Checkout::Session.create(
#  payment_method_types: ["card"],
#  mode: "setup",
#  customer: customer.id
#   success_url: "http://example.com",
#   cancel_url: "http://example.com",
# )

# Exception: Stripe::InvalidRequestError: You can not pass a `customer` in setup mode.

puts checkout_session

# e.g
#
# {
#   "id": "cs_test_nSeAmaYPHYg3uYoJr0AGc8KDPWLX2SnW5ESdiSiNpIwkwb7QjoLBdcUZ",
#   "object": "checkout.session",
#   "billing_address_collection": null,
#   "cancel_url": "http://example.com",
#   "client_reference_id": null,
#   "customer": null,
#   "customer_email": null,
#   "display_items": [

#   ],
#   "livemode": false,
#   "locale": null,
#   "metadata": {
#   },
#   "mode": "setup",
#   "payment_intent": null,
#   "payment_method_types": [
#     "card"
#   ],
#   "setup_intent": "seti_1GDl7YCmti5jpytU0I0ByfjD",
#   "submit_type": null,
#   "subscription": null,
#   "success_url": "http://example.com"
# }



puts <<~HTML
<script src="https://js.stripe.com/v3/"></script>

<script>

var stripe = Stripe('#{ENV['STRIPE_PUBLIC_KEY']}');

stripe.redirectToCheckout({
  // Make the id field from the Checkout Session creation API response
  // available to this file, so you can provide it as parameter here
  // instead of the placeholder.
  sessionId: '#{checkout_session.id}'
}).then(function (result) {
  // If `redirectToCheckout` fails due to a browser or network
  // error, display the localized error message to your customer
  // using `result.error.message`.
});

</script>
HTML

# e.g
#
# Save this html file in your machine and access
#
# <script src="https://js.stripe.com/v3/"></script>
#
# <script>
#
# var stripe = Stripe('pk_test_wxxxxxxxxxxxxxxxxxxxxxx');
#
# stripe.redirectToCheckout({
#   // Make the id field from the Checkout Session creation API response
#   // available to this file, so you can provide it as parameter here
#   // instead of the placeholder.
#   sessionId: 'cs_test_nSeAmaYPHYg3uYoJr0AGc8KDPWLX2SnW5ESdiSiNpIwkwb7QjoLBdcUZ'
# }).then(function (result) {
#   // If `redirectToCheckout` fails due to a browser or network
#   // error, display the localized error message to your customer
#   // using `result.error.message`.
# });
#
# </script>

# Redirect to URL example
# https://checkout.stripe.com/pay/cs_test_fRmOtv2TQjhfo1WmVXCxw68tlckFz6KiIUi2EwvHTACynBckPrfu98L1#fidkdWxOYHwnPyd1blpxYHZxWnJhVDRvd1B3bHRzY2xWSF9ycUJAUVFfZzU1dU9tNkJRTTMnKSd3YGNgd3dgd0p3bGJsayc%2Fa3BpaSknaGxhdic%2FfidicGxhJz8nS0QnKSdocGxhJz8nYzRhMWNnPDQoMGc1NygxPWNmKGc0PDUoNGRjMGFnPTQwMWQwJykndmxhJz8nNTNnMTcxZDMoNzE2MCgxMzwwKDwzY2coYTZgYzM0MjM1ZDU1J3gpJ2dgcWR2Jz9eWHgl

# After checkout


Stripe::Checkout::Session.retrieve(id: checkout_session.id, expand: ["setup_intent", "setup_intent.payment_method"]).setup_intent

# There is payment method

# => #<Stripe::SetupIntent:0x3fc716e16f00 id=seti_1GDlGfCmti5jpytUDumoV2O4> JSON: {
#   "id": "seti_1GDlGfCmti5jpytUDumoV2O4",
#   "object": "setup_intent",
#   "application": null,
#   "cancellation_reason": null,
#   "client_secret": "seti_xxxxxxxxxxxxxxxxxxxxxxxxxx",
#   "created": 1582090721,
#   "customer": null,
#   "description": null,
#   "last_setup_error": null,
#   "livemode": false,
#   "mandate": null,
#   "metadata": {},
#   "next_action": null,
#   "on_behalf_of": null,
#   "payment_method": {"id":"pm_1GDlLxCmti5jpytUaLbokfjU","object":"payment_method","billing_details":{"address":{"city":null,"country":"JP","line1":null,"line2":null,"postal_code":null,"state":null},"email":"example@gmail.com","name":"yuma inaura","phone":null},"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":"pass"},"country":"US","exp_month":11,"exp_year":2030,"fingerprint":"3Dnj9E30BUfyFTcl","funding":"credit","generated_from":null,"last4":"4242","three_d_secure_usage":{"supported":true},"wallet":null},"created":1582091050,"customer":null,"livemode":false,"metadata":{},"type":"card"},
#   "payment_method_options": {"card":{"request_three_d_secure":"automatic"}},
#   "payment_method_types": [
#     "card"
#   ],
#   "single_use_mandate": null,
#   "status": "succeeded",
#   "usage": "off_session"
# }

image

succeeded and redirect to

image

JP

Stripe API API で WebFormで決済情報・カード情報を登録するためのセッションを発行する

Original by Github issue

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

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

bundle install 実行 エラー

bundle install を実行後次のエラーが発生

※初心者向けです
アウトプットの練習の為、記述しています。

開発環境 
ruby 2.5.1
rails 5.2.4

gemに追記したいファイル

gem 'carrierwave'
gem 'mini_magick'

Gemファイルに追記後下記のエラーがターミナルで発生

There was an error parsing `Gemfile`: unterminated string meets end of file - gem 'mini_magick
            ^. Bundler cannot continue.
from /Users/yasumasaikeda/Desktop/projects/mini_listing/Gemfile:64
gem 'carrierwave'
gem 'mini_magick

上記のエラーメッセージは記述によるミスでgemファイルの( ' )が最後抜けてました。
かなり初歩的なミスですがエラーの内容をしっかり見て、指定のファイルを辿れば解決できました。

正しいgemファイルの記述

gem 'mini_magick'

以上、です

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

【ruby2.5】今日の日付を取得する方法

Dateクラスを初めて知ったので、備忘録用にまとめました。

年/月/日/曜日の取得方法

require "date"

puts Date.today #出力結果 2020-02-20

puts Date.today.year          # 2020
puts Date.today.month         # 2
puts Date.today.day           # 20
puts Date.today.wday          # 4

todayメソッドを用いて、
dateクラスから今日の日付情報を含んだインスタンスを作成する→Date.today

Date.todayにそれぞれyear、month、day、wdayメソッドを用いて情報を取得する

曜日の取得方法

曜日に関しては、曜日情報を含んだ引数が取得されるので、
言葉に変換する場合は例えば下記のように取得できます。

require "date"

date = Date.today.wday
days = ["日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"]
puts days[date] #出力結果 木曜日

以上です。

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

【binarysearch.io】Codewars と併用すると良いかもしれないオンラインプログラミング学習サイト

Twitter でのつぶやきで知ったオンラインのプログラミング学習サイトです。

  • Java, Ruby, Python, C++, JavaScript に対応しています (2020/02/20現在)。
  • チャットしながら複数人で解答を作っていくという醍醐味があるみたいですが、今回は一人で(他人のチャットに邪魔されずに)学習する方法をお教えします。
  • Codewars よりも問題の質が安定しているという印象を受けました。(公式が作成した問題のみ)
  • 競技プログラミングではないので、プログラミング学習のドリルとして使う、プログラミングのレベルアップに使う等の用途に向いていると思います。

1. https://binarysearch.io/ にアクセスし「Get Started」をクリックしてサインアップを行います。

image.png

2. 「Full Name」「Username」「Email」「Password」を入力して「Sign up for Binary Search」をクリックします。

image.png

3. 2で登録した「Email」に登録確認用メールが送信された旨が表示されるので、メールアプリを開いて確認用のリンクをクリックします。

image.png

image.png

4. メールアドレスの確認が完了したら「Start」ボタンをクリックします。

image.png

5. https://binarysearch.io/ のログイン後の画面が表示されます。問題を解くには「Create room」ボタンを押します。

image.png

6. Difficulty で問題の難易度を選びます。外国人の方からチャットで話かかけられたりしないように Capacity を「1 player」にしておく(または Private にしておく)と良いかもしれません。

image.png

7. room を作成したら「Start Room」ボタンを押して問題を開始します。(今回は Easy で作成しました)

image.png

8. 「引数として与えられた配列の長さが1かどうかを返す」よう関数を完成させなさい、という問題です(注記:こんな簡単すぎる問題ばかりではありません)。解答言語としてJavaScriptを 選択しました。

image.png

9. 解答を作成したら「Run▶」ボタンをクリックします。「Correct!」と表示されたら「Submit code」ボタンをクリックします。

image.png

image.png

10. 問題によっては、「Solution」というタブに解法・解説が提示されることがあります。(この問題は簡単すぎなので解説がないんだと思います)

image.png

11. 別の問題を解くには「Start Room」ボタンを再度押して次の問題を開始します。部屋を抜けるには画面右上の×ボタンをクリックします。

image.png

最後に

公式の問題ばかりなので(?)問題の質が安定している(均一)なのが良いですね。気になったのは同じレベルで学習を続けていると同じ問題が出てくることがあることですが、そのまま解いちゃえば良いので大きな問題ではないと思います。

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

【binarysearch.io】Codewars と併用すると良いかもしれないオンラインプログラミング学習サイト (Master algorithms together)

Twitter でのつぶやきで知ったオンラインのプログラミング学習サイトです。

  • Java, Ruby, Python, C++, JavaScript に対応しています (2020/02/20現在)。
  • チャットしながら複数人で解答を作っていくという醍醐味があるみたいですが、今回は一人で(他人のチャットに邪魔されずに)学習する方法をお教えします。
  • Codewars よりも問題の質が安定しているという印象を受けました。(公式が作成した問題のみ)
  • 競技プログラミングではないので、プログラミング学習のドリルとして使う、プログラミングのレベルアップに使う等の用途に向いていると思います。

1. https://binarysearch.io/ にアクセスし「Get Started」をクリックしてサインアップを行います。

image.png

2. 「Full Name」「Username」「Email」「Password」を入力して「Sign up for Binary Search」をクリックします。

image.png

3. 2で登録した「Email」に登録確認用メールが送信された旨が表示されるので、メールアプリを開いて確認用のリンクをクリックします。

image.png

image.png

4. メールアドレスの確認が完了したら「Start」ボタンをクリックします。

image.png

5. https://binarysearch.io/ のログイン後の画面が表示されます。問題を解くには「Create room」ボタンを押します。

image.png

6. Difficulty で問題の難易度を選びます。外国人の方からチャットで話かかけられたりしないように Capacity を「1 player」にしておく(または Open to public を Private にしておく)と良いかもしれません。

image.png

7. room を作成したら「Start Room」ボタンを押して問題を開始します。(今回は Easy で作成しました)

image.png

8. 「引数として与えられた配列の長さが1かどうかを返す」よう関数を完成させなさい、という問題です(注記:こんな簡単すぎる問題ばかりではありません)。解答言語としてJavaScriptを 選択しました。

image.png

9. 解答を作成したら「Run▶」ボタンをクリックします。「Correct!」と表示されたら「Submit code」ボタンをクリックします。

image.png

image.png

10. 問題によっては、「Solution」というタブに解法・解説が提示されることがあります。(この問題は簡単すぎなので解説がないんだと思います)

image.png

11. 別の問題を解くには「Start Room」ボタンを再度押して次の問題を開始します。部屋を抜けるには画面右上の×ボタンをクリックしてから「Leave」を選択します。

image.png

最後に

公式の問題ばかりなので(?)問題の質が安定している(均一)なのが良いですね。気になったのは同じレベルで学習を続けていると同じ問題が出てくることがあることですが、そのまま解いちゃえば良いので大きな問題ではないと思います。

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

[読書] オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方

なんとなくオブジェクト指向を導入し、妙に DRY にこだわった結果、相互依存が増えて見通しの悪い巨大なオブジェクトの中に手続き的なコード分岐が大量に存在するコードに直面することは、比較的 Rails で開発しているとあることかもしれない。オブジェクト指向は教条主義的になりがちだが、現実正解にある巨大なゴチャゴチャコードからどうやって分離するかという観点では実践的なガイドとなる。実質的にはリファクタリングをオブジェクト指向を用いてどうすすめるかという観点で良い助けになるだろう。とくにオブジェクト間の処理の流れをシーケンス図にして整理するセクションについては、とてもわかりやすい。積極的にシーケンス図を活用すべきである。
しかし、いくつかネックとなるポイントも存在する。1/5 のボリュームはオブジェクトをどうテストするかという観点でやや薄まった内容で記載さてれているし、翻訳も少し不自然さはあるかもしれない。また、適応されているデザインパターンもごく一部で網羅しているわけではなく、思ったよりボリュームが足りないというのが率直な感想。最後に、本書は自転車のメカニズムの構造に興味がない人は合わないかもしれないという点も補足しておく。

★★★★☆ 4/5

https://amzn.to/32bQUpE

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

[Ruby] irb や pry で例外時のバックトレースを表示する

TL;DR

irb や pry で例外時のバックトレースを表示するには

  • $@ を参照する。
  • wtf コマンドを使う (pry 限定) 。
    • ちなみに、現実世界の会話では wtf (What the Fxck!) なんてあまり使わないように ?‍♀️

バージョン情報

$ ruby -v
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin18]

$ bin/rails -v
Rails 6.0.1  

実例

以下のモデルを作成した。

app/models/hunter.rb
class Hunter < ApplicationRecord
  validates name, presence: true
end

早速 bin/rails c で動作を確認してみると謎のエラーが発生した。

irb(main):008:0> Hunter.create!(name: 'クラピカ')
Traceback (most recent call last):
        1: from (irb):8
NoMethodError (undefined method `Hunter' for #<Hunter:0x00007f8a322f52d8>)

Hunter#Hunter というメソッドがないって言われる……。なんだこの HUNTER×HUNTER みたいなへんちくりんなエラーは ?

それにしても、他に情報が何も出力されないのでエラーの原因がさっぱりわからない。こういうときはメソッドの呼び出し状況であるバックトレースを出力する。Ruby では $@ という特殊変数にバックトレースを参照することができる。

irb(main):002:0> puts $@
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
# 略
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activerecord-6.0.1/lib/active_record/persistence.rb:55:in `create!'
(irb):8:in `irb_binding'
# 略
-e:1:in `<main>'
=> nil

バックトレースに validate というキーワードがあったのでバリデーション周りを疑ってみる。すると本来シンボルであるはずの validates の引数がシンボルになっていなかったのが原因だと判明した!

app/models/hunter.rb
class Hunter < ApplicationRecord
  # name が self.name すなわち Hunter.name と解釈されて "Hunter" を返していた。
  # name を :name に修正した。
  validates :name, presence: true
end

しかしこの方法はちょっと不便だ。もう一度 $@ を参照しようとすると空になってしまう。

irb(main):002:0> puts $@
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
# 略
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activerecord-6.0.1/lib/active_record/persistence.rb:55:in `create!'
(irb):8:in `irb_binding'
# 略
-e:1:in `<main>'
=> nil
irb(main):003:0> puts $@

=> nil

そこで irb をより強力にしたツールである pry を使う。Gemfile に pry-rails を追加して bundle install しておく。

Gemfile
group :development, :test do
  gem 'pry-rails'
end

bin/rails c で動作を確認してみる。

[1] pry(main)> Hunter.create!(name: 'クラピカ')
NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
from /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'

wtf と入力すると、例外のバックトレースが表示される。すべてではなく先頭から数行が表示される。

[2] pry(main)> wtf
Exception: NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
--
0: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
1: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
2: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
3: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
4: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:429:in `block in make_lambda'

もう一度入力してもバックトレースが表示される。おもしろいことに wtf!wtf!!? のように wtf の後に !? をつけると、つけた量に比例して表示されるバックトレースの行数が増える。

[3] pry(main)> wtf!
Exception: NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
--
0: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
1: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
2: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
3: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
4: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:429:in `block in make_lambda'
5: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:201:in `block (2 levels) in halting'
6: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:607:in `block (2 levels) in default_terminator'
7: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:606:in `catch'
8: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:606:in `block in default_terminator'
9: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:202:in `block in halting'

バックトレースを最後まで表示したい場合は wtf -v と入力する。

[4] pry(main)> wtf -v
Exception: NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
--
 0: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
 1: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
 2: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
 3: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
# 略
73: /Users/killua/.rbenv/versions/2.6.5/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
74: /Users/killua/.rbenv/versions/2.6.5/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
75: -e:1:in `<main>'

参考

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

[Ruby][初心者向け] コンソールで例外が発生したときは、バックトレースを表示して原因を調べよう

typo など特に凡ミス対策に有効です ?

TL;DR

  • コンソール (irb や pry) で例外が発生しても、バックトレースが表示されないので原因がわからないことがある。
  • コンソールで例外時のバックトレースを表示するには
    • 特殊変数 $@ を参照する。
    • wtf コマンドを使う (pry 限定) 。
      • ちなみに、現実世界の会話では wtf (What the Fxck!) なんてあまり使わないように ?‍♀️

バージョン情報

$ ruby -v
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin18]

$ bin/rails -v
Rails 6.0.1  

実例

以下のモデルを作成した。

app/models/hunter.rb
class Hunter < ApplicationRecord
  validates name, presence: true
end

早速 bin/rails c (irb) で動作を確認してみると謎のエラーが発生した。

irb(main):008:0> Hunter.create!(name: 'クラピカ')
Traceback (most recent call last):
        1: from (irb):8
NoMethodError (undefined method `Hunter' for #<Hunter:0x00007f8a322f52d8>)

Hunter#Hunter というメソッドがないって言われる……。なんだこの HUNTER×HUNTER みたいなへんちくりんなエラーは ?

それにしても、他に情報が何も出力されないのでエラーの原因がさっぱりわからない。こういうときはメソッドの呼び出し状況であるバックトレースを出力する。Ruby では $@ という特殊変数でバックトレースを参照することができる。

irb(main):002:0> puts $@
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
# 略
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activerecord-6.0.1/lib/active_record/persistence.rb:55:in `create!'
(irb):8:in `irb_binding'
# 略
-e:1:in `<main>'
=> nil

バックトレースに validate というキーワードがあったのでバリデーション周りを疑ってみる。すると本来シンボルであるはずの validates の引数がシンボルになっていなかったのが原因だと判明した!

app/models/hunter.rb
class Hunter < ApplicationRecord
  # name が self.name すなわち Hunter.name と解釈されて "Hunter" を返していた。
  # name を :name に修正した。
  validates :name, presence: true
end

しかしこの方法はちょっと不便だ。もう一度 $@ を参照しようとすると空になってしまう。

irb(main):002:0> puts $@
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
# 略
/Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activerecord-6.0.1/lib/active_record/persistence.rb:55:in `create!'
(irb):8:in `irb_binding'
# 略
-e:1:in `<main>'
=> nil
irb(main):003:0> puts $@

=> nil

そこで irb をより強力にしたツールである pry を使う。Gemfile に pry-rails を追加して bundle install しておく。

Gemfile
group :development, :test do
  gem 'pry-rails'
end

bin/rails c で動作を確認してみる。

[1] pry(main)> Hunter.create!(name: 'クラピカ')
NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
from /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'

wtf と入力すると、例外のバックトレースが表示される。すべてではなく先頭から数行が表示される。

[2] pry(main)> wtf
Exception: NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
--
0: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
1: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
2: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
3: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
4: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:429:in `block in make_lambda'

もう一度入力してもバックトレースが表示される。おもしろいことに wtf!wtf!!? のように wtf の後に !? をつけると、つけた量に比例して表示されるバックトレースの行数が増える。

[3] pry(main)> wtf!
Exception: NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
--
0: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
1: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
2: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
3: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
4: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:429:in `block in make_lambda'
5: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:201:in `block (2 levels) in halting'
6: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:607:in `block (2 levels) in default_terminator'
7: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:606:in `catch'
8: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:606:in `block in default_terminator'
9: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:202:in `block in halting'

バックトレースを最後まで表示したい場合は wtf -v と入力する。

[4] pry(main)> wtf -v
Exception: NoMethodError: undefined method `Hunter' for #<Hunter:0x00007fe654afd088>
--
 0: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'
 1: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:150:in `block in validate'
 2: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `each'
 3: /Users/killua/myapp/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/validator.rb:149:in `validate'
# 略
73: /Users/killua/.rbenv/versions/2.6.5/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
74: /Users/killua/.rbenv/versions/2.6.5/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
75: -e:1:in `<main>'

参考

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

ActiveAdminのTips

最近ActiveAdminを使用しているのですが
便利な反面, 仕様がわかりづらく, バグでは?と思うことが多いです.
私が躓いたところをいろいろ追加して行こうと思います.

前提環境

activeadmin (1.4.3)

詳細画面

if文の挙動

特定のカラムに値があったら, 表示する.
なければ表示しない. という単純な仕様

shops.rb
ActiveAdmin.register Shop do
  show do
    panel "求人(Job)情報" do
      attributes_table_for shop.job do
        #① idがあれば表示
        if resource.job.job_img1_id.present?
          row :job_img1_id
        end
        #② idがなければ表示
        if resource.job.job_img2_id.nil?
          row :job_img2_id
        end
        #③ idがあれば表示  (ここ挙動がおかしい!!バグ!!)
        if :job_img3_id.present?
          row :job_img3_id
        end
        #④ idがなければ表示  (ここ挙動がおかしい!!バグ!!)
        if :job_img4_id.nil?
          row :job_img4_id
        end
      end
    end
  end
end

今 job_img[1-4]_id は値がないnull(nil)です.
つまり、写真2と写真4だけが表示されているのが正しい挙動です.

実際の画面を見ると

スクリーンショット 2020-02-20 10.53.44.png
写真1 写真4 => 非表示
写真2 写真3 => 表示
(上下のメイン写真コメントと写真5はここでは無関係)

ここで
写真3は写真1同様に非表示とならなければなりません.同様に
写真4は写真2同様に表示とならなければなりません.
ここでバグがありました.

結論

:シンボル使用した時と, resouceから呼び出した時で, 挙動が正反対になっています.
正しい挙動はresouceを使用した時です.
ifの引数にシンボリックの使用は推奨されません.

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

ActiveAdminのTips& Bugs

最近ActiveAdminを使用しているのですが
便利な反面, 仕様がわかりづらく, バグでは?と思うことが多いです.
私が躓いたところをいろいろ追加して行こうと思います.
日本語の記事が見つからなかったので, 書きました.

前提環境

activeadmin (1.4.3)

一覧画面(Index)

カスタムフィルター(子テーブルの要素をFilter対象とする)

前提

親(Shop) : 子(Job) = 1 : N 関係の時,
親要素に子要素をJOINして, DBのビューのような使い方をしている.

実現したいこと

子(Job)の特定のカラム(ここでは:published)の値でフィルター機能を作りたい

ソースコード

app/models/shop.rb
class Shop < ActiveRecord::Base
  has_many :jobs, dependent: :destroy

  # ActiveAdmin用のscope 
  scope :join_jobs, -> { joins(:jobs)}
  scope :active_admin_custom_filter, -> (filter) {
    # gem ransackerのバグのため、ここで数値へ変換
    # filter =  filter == "掲載中" ? 1 :0
    self.where('jobs.published = ? ',filter)
  }

  # 別にprivateでなくても良いが
  private
  # ActiveAdmin用のfilterカスタマイズ
  def self.ransackable_scopes(auth_object = nil)
    [:active_admin_custom_filter]
  end
app/admin/shops.rb
ActiveAdmin.register Shop do
  # gemのransackerにバグがあり, 自作scopeに数値を渡せない. initilizarの要修正
  filter :active_admin_custom_filter, label: "掲載(求)", as: :select, collection: proc{ [%(掲載中 1), %w(掲載停止 0)] }
  # initilizarを修正していない時はこっち
  # filter :active_admin_custom_filter, label: "掲載(求)", as: :select, collection: proc{ [%(掲載中 掲載中), %w(掲載停止 掲載停止)] }
  # カスタムフィルターだと, as: を指定する必要がある. よって, 下では表示すらされない
  # filter :active_admin_custom_filter, label: "掲載(求)

  # override
  controller do
    def scoped_collection
      end_of_association_chain.join_jobs
    end
  end
end

gem ranscakerの不具合はこちらの記事を参照させていただきました.

参照1
参照2

画面とまとめ

スクリーンショット 2020-02-20 16.50.57.png

これで親(Shop)にはない,子(Job)の要素(publishedカラム)でfilterができました.

参照

詳細画面(Show)

if文の挙動

特定のカラムに値があったら, 表示する.
なければ表示しない. という単純な仕様

app/admin/shops.rb
ActiveAdmin.register Shop do
  show do
    panel "求人(Job)情報" do
      attributes_table_for shop.job do
        #① idがあれば表示
        if resource.job.job_img1_id.present?
          row :job_img1_id
        end
        #② idがなければ表示
        if resource.job.job_img2_id.nil?
          row :job_img2_id
        end
        #③ idがあれば表示  (ここ挙動がおかしい!!バグ!!)
        if :job_img3_id.present?
          row :job_img3_id
        end
        #④ idがなければ表示  (ここ挙動がおかしい!!バグ!!)
        if :job_img4_id.nil?
          row :job_img4_id
        end
      end
    end
  end
end

今 job_img[1-4]_id は値がないnull(nil)です.
つまり、写真2と写真4だけが表示されているのが正しい挙動です.

実際の画面を見ると

スクリーンショット 2020-02-20 10.53.44.png
写真1 写真4 => 非表示
写真2 写真3 => 表示
(上下のメイン写真コメントと写真5はここでは無関係)

ここで
写真3は写真1同様に非表示とならなければなりません.同様に
写真4は写真2同様に表示とならなければなりません.
ここでバグがありました.

結論

:シンボル使用した時と, resouceから呼び出した時で, 挙動が正反対になっています.
正しい挙動はresouceを使用した時です.
ifの引数にシンボリックの使用は推奨されません.

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

Rubyで配列の要素を検索置換する方法

WEB企業の研修中に学んだ事のアウトプットです。
配列の要素を検索し、その要素を別のオブジェクトで置換する方法です。

一般的な方法

array = ["foo","bar","baz"]

# arrayから"bar"を検索して"hoge"で置換します。
array.map!{|x| x=="bar" ? "hoge" : x}

puts array
#=> ["foo","hoge","baz"]

微妙な方法

※この方法でも検索置換できますが、検索ワードが配列に存在しない場合エラーになります。

array = ["foo","bar","baz"]

# arrayから"bar"を検索して"hoge"で置換します。
array[array.index("bar")] = "hoge"

puts array
#=> ["foo","hoge","baz"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsアプリをHerokuにデプロイしたらクラッシュしてたときの対処法

開発環境では問題なく動いていたアプリが、満を持してデプロイしたときにクラッシュしてたら精神的に結構きますよね

正直、辛い。

今回は、Rails5のアプリをHerokuにデプロイした際にクラッシュを起こしていたので、そのとき取った行動をメモとして残しておきます。

エラー画面

image.png

We're sorry, but something went wrong.
If you are the application owner check the logs for more information.

日本語でおk(死語)

このエラー文自体を検索し、色んなエラー時対応の記事を読み込んでいくと、
何にしろログを見なっせ(書いてある)ということなので、
$ heroku logsを叩きました。

こちらの記事はとても参考になりました。

Qiita/[Ruby on Rails Tutorial]Herokuにデプロイ後Application error[H10 (App crashed)]が発生した時の対処法

$ heroku logs
2020-02-19T13:24:10.854459+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/runner.rb:138:in `load_and_bind'
2020-02-19T13:24:10.854460+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/single.rb:87:in `run'
2020-02-19T13:24:10.854460+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/launcher.rb:174:in `run'
2020-02-19T13:24:10.854461+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/cli.rb:77:in `run'
2020-02-19T13:24:10.854461+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/bin/puma:10:in `<top (required)>'
2020-02-19T13:24:10.854462+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/bin/puma:23:in `load'
2020-02-19T13:24:10.854462+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/bin/puma:23:in `<top (required)>'
2020-02-19T13:24:10.941495+00:00 heroku[web.1]: State changed from starting to crashed
2020-02-19T13:24:10.946163+00:00 heroku[web.1]: State changed from crashed to starting
2020-02-19T13:24:10.919655+00:00 heroku[web.1]: Process exited with status 1
2020-02-19T13:24:14.912178+00:00 heroku[web.1]: Starting process with command `bundle exec puma -C config/puma.rb`
2020-02-19T13:24:17.577966+00:00 app[web.1]: Puma starting in single mode...
2020-02-19T13:24:17.587312+00:00 app[web.1]: * Version 3.9.1 (ruby 2.5.7-p206), codename: Private Caller
2020-02-19T13:24:17.587332+00:00 app[web.1]: * Min threads: 5, max threads: 5
2020-02-19T13:24:17.587335+00:00 app[web.1]: * Environment: production
2020-02-19T13:24:19.870761+00:00 app[web.1]: ! Unable to load application: LoadError: No such file to load -- URI.rb
2020-02-19T13:24:19.870872+00:00 app[web.1]: bundler: failed to load command: puma (/app/vendor/bundle/ruby/2.5.0/bin/puma)
2020-02-19T13:24:19.870924+00:00 app[web.1]: LoadError: No such file to load -- URI.rb
2020-02-19T13:24:19.870926+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `require'
2020-02-19T13:24:19.870926+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `block in require'
2020-02-19T13:24:19.870927+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:258:in `load_dependency'
2020-02-19T13:24:19.870927+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `require'
2020-02-19T13:24:19.870928+00:00 app[web.1]: /app/app/controllers/application_controller.rb:5:in `<class:ApplicationController>'
2020-02-19T13:24:19.870928+00:00 app[web.1]: /app/app/controllers/application_controller.rb:1:in `<top (required)>'
2020-02-19T13:24:19.870929+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `require'
2020-02-19T13:24:19.870929+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `block in require'
2020-02-19T13:24:19.870930+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:258:in `load_dependency'
2020-02-19T13:24:19.870930+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `require'
2020-02-19T13:24:19.870930+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:379:in `block in require_or_load'
2020-02-19T13:24:19.870931+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:36:in `block in load_interlock'
2020-02-19T13:24:19.870931+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies/interlock.rb:12:in `block in loading'
2020-02-19T13:24:19.870932+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/concurrency/share_lock.rb:149:in `exclusive'
2020-02-19T13:24:19.870932+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies/interlock.rb:11:in `loading'
2020-02-19T13:24:19.870933+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:36:in `load_interlock'
2020-02-19T13:24:19.870933+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:357:in `require_or_load'
2020-02-19T13:24:19.870934+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:335:in `depend_on'
2020-02-19T13:24:19.870934+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:251:in `require_dependency'
2020-02-19T13:24:19.870934+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:476:in `block (2 levels) in eager_load!'
2020-02-19T13:24:19.870935+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:475:in `each'
2020-02-19T13:24:19.870935+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:475:in `block in eager_load!'
2020-02-19T13:24:19.870935+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:473:in `each'
2020-02-19T13:24:19.870936+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:473:in `eager_load!'
2020-02-19T13:24:19.870936+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:354:in `eager_load!'
2020-02-19T13:24:19.870937+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/application/finisher.rb:67:in `each'
2020-02-19T13:24:19.870937+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/application/finisher.rb:67:in `block in <module:Finisher>'
2020-02-19T13:24:19.870937+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/initializable.rb:30:in `instance_exec'
2020-02-19T13:24:19.870938+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/initializable.rb:30:in `run'
2020-02-19T13:24:19.870938+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/initializable.rb:59:in `block in run_initializers'
2020-02-19T13:24:19.870939+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:228:in `block in tsort_each'
2020-02-19T13:24:19.870939+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
2020-02-19T13:24:19.870939+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:431:in `each_strongly_connected_component_from'
2020-02-19T13:24:19.870940+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:349:in `block in each_strongly_connected_component'
2020-02-19T13:24:19.870940+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:347:in `each'
2020-02-19T13:24:19.870941+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:347:in `call'
2020-02-19T13:24:19.870941+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:347:in `each_strongly_connected_component'
2020-02-19T13:24:19.870942+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:226:in `tsort_each'
2020-02-19T13:24:19.870942+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:205:in `tsort_each'
2020-02-19T13:24:19.870947+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/initializable.rb:58:in `run_initializers'
2020-02-19T13:24:19.870947+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/application.rb:353:in `initialize!'
2020-02-19T13:24:19.870948+00:00 app[web.1]: /app/config/environment.rb:5:in `<top (required)>'
2020-02-19T13:24:19.870948+00:00 app[web.1]: config.ru:3:in `require_relative'
2020-02-19T13:24:19.870948+00:00 app[web.1]: config.ru:3:in `block in <main>'
2020-02-19T13:24:19.870949+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/rack-2.2.2/lib/rack/builder.rb:116:in `eval'
2020-02-19T13:24:19.870949+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/rack-2.2.2/lib/rack/builder.rb:116:in `new_from_string'
2020-02-19T13:24:19.870950+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/rack-2.2.2/lib/rack/builder.rb:105:in `load_file'
2020-02-19T13:24:19.870950+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/rack-2.2.2/lib/rack/builder.rb:66:in `parse_file'
2020-02-19T13:24:19.870951+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/configuration.rb:313:in `load_rackup'
2020-02-19T13:24:19.870951+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/configuration.rb:242:in `app'
2020-02-19T13:24:19.870951+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/runner.rb:138:in `load_and_bind'
2020-02-19T13:24:19.870952+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/single.rb:87:in `run'
2020-02-19T13:24:19.870952+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/launcher.rb:174:in `run'
2020-02-19T13:24:19.870952+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/cli.rb:77:in `run'
2020-02-19T13:24:19.870952+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/bin/puma:10:in `<top (required)>'
2020-02-19T13:24:19.870953+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/bin/puma:23:in `load'
2020-02-19T13:24:19.870953+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/bin/puma:23:in `<top (required)>'
2020-02-19T13:24:20.204439+00:00 heroku[web.1]: State changed from starting to crashed
2020-02-19T13:24:20.185683+00:00 heroku[web.1]: Process exited with status 1
2020-02-19T13:24:21.120419+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=example.herokuapp.com request_id=eb4b6551-949a-4693-9f76-2b0bcd0e92dd fwd="219.100.188.52" dyno= connect= service= status=503 bytes= protocol=https

心折れそう

折れてました。

辛かったので、エラー文の一番最後にあった
heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/"
を、エラーコード記載されてるし後はグーグル先生がどうにかしてくれるだろうと脳死で検索しました。

結果的に分かったのは、

エラー文見ろ

厳しい!!!

目から溢れる汁で滲む画面を読み進めたところ、次の部分に目が留まる(とはいっても序盤の方)。

2020-02-19T13:24:19.870761+00:00 app[web.1]: ! Unable to load application: LoadError: No such file to load -- URI.rb
2020-02-19T13:24:19.870872+00:00 app[web.1]: bundler: failed to load command: puma (/app/vendor/bundle/ruby/2.5.0/bin/puma)
2020-02-19T13:24:19.870924+00:00 app[web.1]: LoadError: No such file to load -- URI.rb

ご丁寧にまでつけてある。

ここでピーンと来て、そういえばネットから次のコードをコピペしたときに、

example_controller.rb
require 'net/https'
require 'uri'        # 'URI'と記載していた

で実行したところ、uriなんてねえよバーカ!と怒られたので、
'uri'=>'URI'と訂正したことを思い出しました。

お ま え か。

その後、正しくrequire 'uri'と訂正すると無事動いてくれました。

エラー文を読もう(結論)

心折れかかったら息抜きも忘れないでください。

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

Ruby on Rails アソシエーションの応用 class_name 【一つのモデルを複数に分岐する】

忘れているかもしれないので、
まずは基本から復習していきましょう!!

基本(1対多)

userが複数の商品(products)を保持している関係だとしたら、

user.rb
class User < ApplicationRecord
  has_many :products
end

productsはuserに保持されているので、

product.rb
class Product < ApplicationRecord
  belongs_to :user
end

userというフォルダに、複数のproductファイルがある感覚と同じ。
スクリーンショット 2020-02-20 0.21.45.png

だからuserは単数形、productは複数形
productは親フォルダのuserに入っているから、belongs_to :user
userフォルダはたくさんのproductを持っているから、 has_many :products

応用(1対多): class_name

じゃあ、productsを保持するuserが、販売者(seller)・購入者(buyer)に分ける場合はどうしましょうか?
seller・buyerにそれぞれモデルは必要ありません。userモデルを分けてあげれば大丈夫です。じゃないとIDが変わって逆に大変ですから。

userはproductsを複数保有しているので同じです。
(次にやります。)

user.rb
class User < ApplicationRecord
  has_many :products
end

productsに紐づくuserを"seller"と"buyer"に分けたいので、下記ようにします。

product.rb
class Product < ApplicationRecord
  belongs_to :seller, class_name: "User", foreign_key: "seller_id"
  belongs_to :buyer, class_name: "User", foreign_key: "buyer_id"
end

「なんじゃこりゃ!!」、となったと思います。要点を抑えたら簡単です。
では解説していきます。

STEP1 モデル名は変えられる。

基礎だと下記のように、ProductはUserに紐づいていました。

product.rb
class Product < ApplicationRecord
  belongs_to :user
end

belongs_to :userと記述されていますが、この"user"という名前を変えられます。
「userじゃなくて、ownerに変えたいなあ??」
このように名前を変えたい場合にclass_nameを使います。

product.rb
class Product < ApplicationRecord
  belongs_to :owner, class_name: "User" # belongs_to :userと同じです。
  #  元はUserモデルだけど(class_name: "User")、ownerって表示に変えますわ(belongs_to :owner)
end

STEP2: Userモデルを分ける

1つだとSTEP1だけで変えられます(まあ、変える必要ないんだけど)。
では、userをsellerとbuyerと2つに分けたい場合、どうすればいいのでしょうか?

まずはproductsテーブルをみてください。
スクリーンショット 2020-02-20 0.33.06.png
productレコードのカラムに"buyer_id(user_id)"と"seller_id(user_id)"があります
このカラムに入れるidを元にuserをsellerとbuyerに分けています。

product.rb
class Product < ApplicationRecord
  belongs_to :seller, class_name: "User", foreign_key: "seller_id"
  # belongs_to :seller, class_name: "User" = (Productに紐づく) Userモデルをsellerと定義するわ。 
  # foreign_key: "seller_id" = user_idはProductレコードの『seller_idカラム』のid番号を使うわ

  belongs_to :buyer, class_name: "User", foreign_key: "buyer_id"
  # belongs_to :buyer, class_name: "User" = (Productに紐づく) Userモデルをbuyerと定義するわ。 
  # foreign_key: "seller_id" = user_idはProductレコードの『buyer_idカラム』のid番号を使うわ
end

これで二つに分けられました。
カラムに紐づくid番号で分けたあげた分けです。

STEP3: Productを複数に分ける

販売中、売却済み、購入した商品に分けます。

販売中だけ習得する
スクリーンショット 2020-02-20 1.47.52.png

売却済みだけ取得する
スクリーンショット 2020-02-20 1.49.58.png

購入した商品だけ取得する
スクリーンショット 2020-02-20 1.51.41.png

user.rb
class User < ApplicationRecord
  # 全部取得
  has_many :products

  # 販売中
  has_many :selling_products, class_name: "Product", foreign_key: "seller_id", -> { where("buyer_id is NULL") }
  # Productモデル名を selling_productsに定義するわ = has_many :selling_products, class_name: "Product"
  # productレコードの”seller_id”カラムに自分のidが入っている、productだけ取得するね = foreign_key: "seller_id" ( userはhas_many: productsだから )
  # でも、buyer_idが入っていないproductだけにする = -> { where("buyer_id is NULL") }

  # 買った商品
  has_many :bought_products, class_name: "Product", foreign_key: "buyer_id"
  # Productモデル名を bought_productsに定義するわ = has_many :bought_products, class_name: "Product"
  # productレコードの”buyer_id”カラムに自分のidが入っている、productだけ取得するね = foreign_key: "buyer_id" ( userはhas_many: productsだから )

  # 売却済み商品
  has_many :sold_products, class_name: "Product", foreign_key: "seller_id", -> { where("buyer_id is not NULL") }
  # Productモデル名を sold_productsに定義するわ = has_many :selling_products, class_name: "Product"
  # productレコードの”seller_id”カラムに自分のidが入っている、productだけ取得するね = foreign_key: "seller_id" ( userはhas_many: productsだから )
  # でも、buyer_idが入ってるproductだけにする = -> { where("buyer_id is not NULL") }
end

まとめ

class_nameでモデルを分ける際は、

まとめ
has_many :〜側のモデルのカラムを使って分ける。

# 今回だと
   has_many :products 
   # productにuser_idを紐づけるので、これを使って分ける。
   # 親元のuserのカラムにproduct_idは入れないので、userのカラムを使って分けることはできない。

まとめ: 1対多なら、 『多』のカラムを使って分けよう ( foreign_key: )

参考リンク

railsアソシエーションオプションのメモ
アソシエーションにおけるclass_nameの定義!

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

Railsでブログアプリケーションの作成時に、postを作成するだけでからのコメントが表示されるのを解消する。

発生した問題

タイトルにもある通り、railsで定番のブログのようなアプリを作っているときに、投稿を作成しそれの詳細をクリックしたらみられるようにshow.html.erbに記述。comment機能も追加した。しかし、投稿を作成するだけで、空のコメントが表示され、削除しようとするとRouting errorになる。

スクリーンショット 2020-02-20 0.43.27.png

解決策

投稿詳細ページ(筆者はshow.html.erb)で、以下のように表示していた。

show.html.erb
<div class="p-comment__list mx-auto">
  <div class="p-comment__listTitle">コメント</div>
  <%= render @post.comments %>
</div>

それを、

show.html.erb
<div class="p-comment__list mx-auto">
  <div class="p-comment__listTitle">コメント</div>
  <%= render @post.comments.select(&:persisted?) %>
</div>

と変更した。
違いに気づいていただけただろうか。@post.commentsを@post.comments.select(&:persisted?)に変更した。

スクリーンショット 2020-02-20 0.48.06.png

直った!!!!!!!

参考URL

https://stackoverflow.com/questions/36680890/rails-empty-comment-on-every-new-post

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

Rubyの例外について無知だったのでざっくりとまとめた

Rubyで例外処理を行うときにわからないことが多すぎたので調べた。

シンプルに

シンプルにbeginrescueを使うパターン。raiseを使うと意図的に例外を出すことができる。
raiseで以下のように例外を出した場合は、例外の種類はRuntimeErrorとなる。

begin 
  raise "例外を発生させたい"
rescue => exception
  puts exception.class
  puts exception.message
  puts exception.backtrace
end

実行結果

RuntimeError
例外を発生させたい
test.rb:2:in `<main>'

raiseRuntimeError以外のエラーを出したいときには以下のように記述する。同時にメッセージを登録することができる。

begin 
  raise StandardError.new('例外を発生させたい')
rescue => exception
  puts exception.class
  puts exception.message
  puts exception.backtrace
end

実行結果

StandardError
例外を発生させたい
test.rb:2:in `<main>'

関数内での例外処理

関数内で例外処理を行いたい場合は以下のように記述する。関数内の場合は、beginのブロックを利用する必要がない。

def test
  raise StandardError.new('例外を発生させたい')
  rescue => exception
    puts exception.class
    puts exception.message
    puts exception.backtrace
end

test()

実行結果

StandardError
例外を発生させたい
test.rb:2:in `test'
test.rb:9:in `<main>'

関数内の関数で例外が発生した場合には外側の関数で捕捉される。

def exception
  raise "関数内での例外"
end

def test
  exception()
rescue => exception
  puts exception.class
  puts exception.message
end

test()

実行結果

RuntimeError
関数内での例外

複数の例外

エラーの種類により処理を分けることができる。以下の例ではStandardErrorRangeErrorの時は上のrescue、それ以外の場合は下のrescueで捕捉される。

begin 
  raise StandardError
  rescue StandardError, RangeError => exception
    puts exception.class
  rescue => exception
    puts exception.class
end

実行結果

StandardError

ZeroDivisionErrorを発生させる

begin 
   1/ 0
  rescue StandardError, RangeError => exception
    puts exception.class
  rescue => exception
    puts exception.class
end

実行結果

ZeroDivisionError

独自のエラー

StandardErrorを継承し、独自のエラークラスを作成することができる。

class MyError < StandardError
  def initialize(msg="Error Message")
    super
  end
end

begin
  raise MyError.new("エラーです")
rescue => exception
  puts exception.class
  puts exception.message
end
MyError
エラーです

まとめ

ざっくりとまとめた。雰囲気は掴めた気がする。

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