20201022のRubyに関する記事は30件です。

少しずつ正規表現を覚えていく①

はじめに

 プログラミングの学習をしてきて、今のところ、いちばんの苦手は、「正規表現」だろう。ググれば、いくらでも正解に出会えるが、せめて何を表しているのか読めるようになりたいので、何回かに分けて、紹介していく。

今日出会った正規表現

正規表現 意味
{n} n回繰り返す
{n,m} n回繰り返す。最大m回まで
[ ] 角括弧の中の文字いずれか一文字にマッチ
[ - ] -は「から(〜)」みたいな意味
. 任意の一字にマッチ
\d 半角数字

さいごに

 きっと、読み方は組み合わせによるものだと思う。一つひとつの意味を理解していく必要がありそう。

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

Ruby on Rails チュートリアルの中で覚えておくべきこと・概念

はじめに

私はWeb系の自社開発企業に転職のためにRailsの勉強をしました!
その勉強の一環でRailsのチュートリアル第6版を実施していて、私はこれが大事 & 覚えておくべきと思ったことを記載します。

  • Railsのチュートリアルをやったけど、あんまり頭に入ってない方
  • Railsのチュートリアルってどんなこと学べるの?と思ってる方
  • Railsでアプリを作成するときの基本を復習、学びたい方 は本記事が参考になるかと思います!

※本記事は5章〜14章までしか網羅してません。(5章からQiitaで記事に纏めて、備忘録にしようと思ったので。。。)
 気が向いたら、1章〜4章も後で更新します。STAR WARS方式で。

RubyとRailsの基礎用語、概念

★gemとは
- RubyGemsが公開してるRubyのパッケージのこと
- それらパッケージを管理するパッケージ管理システムのこと

★bundle install
- Gemfileに記載してあるRailsでの開発に必要なものをInstallするコマンド

★rails db:migrate
- railsで使用するデータベースの構造(テーブル、カラム)を変更するときに利用する機能
 ・rails db:migrateの大まかな使用の流れ
 1. マイグレーションファイルを作成&内容を記載
 2. $ rails db:migrateコマンドでマイグレーションを順番に実行してデータベースに変更を加える

第5章 レイアウトを作成する

5.2.1 アセットパイプライン

アセットパイプラインというのは主に(以下の)3つの主要機能に分かれている。
(以下 ※説明 ★簡単に要約)

[アセットパイプラインの最大のメリット]
本番アプリケーションで効率的になるよう最適化されたアセットも自動に生成されること。(=本番環境でページの読み込みを早くするのを自動的に行なってくれる。)
そうすることにより、「開発環境ではファイルの可読性」を保ち、「本番環境ではアプリの実行速度をあげる」という2つの異なる環境で各々に最強の環境を提供するという両立ができる。
★要するに、「開発環境」も「本番環境」も最適化されて、最高に効率/利便性が良いということ

  1. アセットディレクトリ
      ※開発に必要なもの(アセット)を置いておくディレクトリ

    • app/assets: 現在のアプリケーション固有のアセット
    • lib/assets: あなたの開発チームによって作成されたライブラリ用のアセット
    • vendor/assets: サードパーティのアセット(デフォルトでは存在しません)
  2. マニフェストファイル
      ※上記の「アセット」マニフェストファイルを一つのファイルに纏めることができる。

    • アセットを纏める処理を行うのはSprocketsというgem
    • マニフェストファイルで纏めれるのはCSSとJavaScript。(※画像ファイルには適応されない。)
 *= require_tree . #app/assets/stylesheetsの中の全てのCSSファイルを含むようにしてる
 *= require_self  #マニフェストファイル(application.css)自身も含むようにしてる

マニフェストファイルの中の上記のようなコメントをSporocketsが読み込んでいる。

3.プリプロセッサエンジン
  ※Railsはプリプロセッサエンジンを介して、アセットを実行し、ブラウザに配信できるように、マニフェストファイルを用いて結合し、サイト用に準備する

★要するにアセットを一つに纏めて、ブラウザで使いやすくしてる

5.3.4 リンクのテスト

    assert_select "a[href=?]", help_path, 

※help_pathが"/help”という定義は行っている前提。
上記のコードは、以下の"/help”パスが存在するか確認するテスト。
"a[href=?]"の?の部分がhelp_pathに置き換わる。(“/help”が存在するか確認)

<a href="/help”>...</a>
    assert_select "a[href=?]", root_path, count: 2

のcountはroot_pathへのリンクは2つあり、2つテストすることを指す。

assert_selectには色々な指定の仕方があります。
以下がそのうちのいくつかの代表例です。
image.png

第5章のまとめ

 - Railsのパーシャルは効率化のために使われ、別ファイルにマークアップを切り出すことができる。
 - Bootstrapフレームワークを使うと、レスポンシブで良いデザインを素早く実装できる
 - SassとAsset Pipelineは、(開発効率のために切り分けられた)CSSの冗長な部分を圧縮し、本番環境に最適化した結果を出力する
 - Railsのルーティングは自由にルールを定義できて、そのときに名前付きルートも使えるようなる。
 - 統合テストはページ感遷移を効率的にシュミレーションできる。

第6章 ユーザーのモデルを作成する

6.1 Userモデル

Active Record:データベースとやりとりをするデフォルトのRailsライブラリ

モデルを作成するときは、generate modelというコマンドを使います。
例) nameやemailといった属性を付けたUserモデルの作成

$ rails generate model User name:string email:string

[Railsの命名慣習]
- コントローラ名:複数形(例:Users)
- モデル名:単数形(例:User)
  └モデルは一人のユーザを表すから
- テーブル名:複数形
  └テーブル(DB)は複数のユーザの情報を持つから

6.1.3 ユーザーオブジェクトを作成する

  • user = User.new: インスタンスの生成
  • user.save: モデルの保存 ※下記の>>はrails console上で実行してることを意味する。
>> user = User.new(name: "Michael Hartl", email: "michael@example.com")
>> user.save
  • User.create: Active Recordを通じて、モデルの生成と保存を同時に行う方法
>> User.create(name: "Another Sky”, email: "anothersky@example.org")
  • モデルへのアクセス (<オブジェクト名>.<キー>) 例)
>> user.name
=> "Michael Hartl"
>> user.email
=> "mhartl@example.com"
>> user.updated_at
=> Mon, 23 May 2016 19:05:58 UTC +00:00
  • Active Recordでデータが形成された順で検索する方法
>> User.find(1)
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.com",
created_at: "2019-08-22 01:51:03", updated_at: "2019-08-22 01:51:03">
  • Active Recordで特定の属性(データ)でユーザーを検索する方法
>> User.find_by(email: "michael@example.com")
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.com",
created_at: "2019-08-22 01:51:03", updated_at: "2019-08-22 01:51:03">

6.2 ユーザーを検証する

Active Recordでよく使われるValidation(検証)ケース
1. Presence(存在性)
2. Length(長さ)
3. Format(フォーマット)
4. Uniqueness(一意性)

テスト駆動開発のテストの進め方
1. 有効なモデルのオブジェクトを作成
2. その属性のうちの1つを有効でない属性に意図的に変更
3. バリデーションで失敗するかどうかをテストする
4. 念のため、最初に作成時の状態に対してもテストを書いておき、最初のモデルが有効であるかどうかも確認
5. 4.のテストすることで、バリデーションのテストが失敗したとき、バリデーションの実装に問題があったのか、オブジェクトそのものに問題があったのかを確認することができる

rails test:models: モデルに関するテストだけを走らせるコマンド

$ rails test:models

6.3.1 ハッシュ化されたパスワード

マイグレーション名は自由に指定できる。
末尾を指定(to_usersに)しておくことで、usersテーブルにカラムを追加するマイグレーションがRailsによって自動的に作成される。

例)add_password_digest_to_usersというマイグレーションファイルを生成するためには、次のコマンドを実行します。

$ rails generate migration add_password_digest_to_users password_digest:string

6章のまとめ

  • Active Recordを使うと、データモデルを作成したり、操作したりするための多くのメソッドが使える
  • Active RecordのValidationを使うと、モデルに対して制限を追加できる
  • よくあるValidationは、「存在するか」「長さ」「フォーマット」
  • データベースにインデックスを追加すると検索効率が飛躍的に向上するし、データベースレベルでの一意性を保証するためにもインデックスを使える。

第7章 ユーザー登録

7.3.3 エラーメッセージ

「shared」: 複数のビューで使われるパーシャルは専用のディレクトリ

7.3.4 失敗時のテスト

assert_select: テストの対象がCSSの
・クラスの場合→div#CSSのid名

assert_select 'div#error_explanation'

クラスの場合→div.CSSのクラス名

assert_select 'div.field_with_errors'

7.6.1 本章のまとめ

  • debugメソッドを使うことで、有意なデバッグ情報を表示できる
  • Sassのmixin機能を使うと、CSSのルールをまとめたり、変数のように他の場所でもmixinで指定したCSS情報を使用できる
  • Railsでは簡単に標準的なRESTfulなURLを通した、データ管理が可能
  • form_withヘルパーは、Active Recordのオブジェクトに対応したフォームを作成する
  • flash変数を使うと、一時的なメッセージを表示できる
  • 統合テストを使うと、送信フォームの振る舞いを検証したり、バグの発生を検知できる

第8章 基本的なログイン機構

8.1 セッション

  • HTTPはステートレスなプロトコル

    • 状態管理がないプロトコルということ
    • 前後のリクエストの情報を全く利用せず、独立したトランザクション(処理)として扱われる
    • なのでHTTPプロトコル内「には」、ユーザのID等の情報を保持する「手段」がない
    • ユーザ情報などをWebアプリケーション上で管理する際は、「セッション(Session)」で半永続的な接続をクライアントとサーバ間に別途設定する必要がある

8.1.5 フラッシュのテスト

assert_templateとは:assert_template後にあるURLがビューを描画しているかをテストする。
※下記の場合は、sessions/newがビューを描画してるかのテスト

    assert_template 'sessions/new'

コラム 8.1. 「||=」とは何か?

Rubyではnilとfalseを覗いて、あらゆるオブジェクトがtrueになるように設計されている。
Rubyでは、||演算子をいくつも連続して式の中で使う場合、項を左から順に評価し、最初にtrueになった時点で処理を終えるように設計されてる。
この評価法は短絡評価(short-circuit evaluation)と呼ぶ。

&&演算子も似たような設計になってるが、項を左から評価して最初にfalseになった時点で処理を終了する点が異なる点である。

8.2.4 レイアウトの変更をテストする

.& safe navigation演算子(または"ぼっち演算子) 
Rubyのぼっち演算子を使うと、obj && obj.methodのようなパターンをobj&.methodのように凝縮した形で書けます。
例を上げると、以下のような論理演算子コードが

    if user && user.authenticate(params[:session][:password])

以下のように簡略化できます。

    if user&.authenticate(params[:session][:password])

&& user.authenticate&.authenticate と簡略化できています。
Ruby初学者としては簡略化しすぎでは?と少し混乱するのですが、
ぼっち演算子は使用されることが多いようなので、自分で使ってみて覚える努力が必要そうです。

8.4.1 本章のまとめ

  • Railsのsessionメソッドを使うと、ページ遷移時の状態を保持できる。一時的な状態の保持にはcookiesも使える。(※今後あらゆるブラウザがクロスドメインでのcookies共有を禁止にするため、Rakute○のドメインで取得した、Rakute○IDなどを別ドメイン(Rakute○以外のドメイン)では使用できなくなる。)
  • sessionメソッドを使うと、ユーザIDなどをブラウザに一時保存できる
  • テスト駆動開発(TDD)はレグレッションバグを防ぐときに便利
  • 統合でストでは、ルーティング、DBの更新、レイアウトの変更が正常に実施されてるかテストできる

9章 発展的なログイン機構

9.1.1 記憶トークンと暗号化

cookieを盗み出す有名な方法は4つある。
1. 管理の甘いネットワークを通過するネットワークパケットからパケットスニッファという特殊なソフトで直接cookieを取り出す
2. DBから記憶トークンを取り出す
3. クロスサイトスクリプティング(XSS)を使う
4. ユーザがログインしてるPCやスマホを直接操作してアクセスを奪い取る

1.の対処は7.5のSSLをサイトに対応すること
2.の対応は本チュートリアルでは、DBには記憶トークンをハッシュ化して保存してること
3.の対応はRailsでは自動的に対策されてる
4.システム側で根本的防衛策を講じることは不可能

■attr_accessorの利用用途
読み取りも書き込みもできるオブジェクトの属性を定義したい時
 
・name, descriptionという属性を持つUserオブジェクトを定義の仕方
Railsの場合

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.string :description

      t.timestamps null: false
    end
  end
end

DBを扱わない純粋なRubyコードの場合

class User
  attr_accessor :name, :description
end

ちなみに attr_reader は読み出し専用の属性を定義したいときに使い、
attr_writer は書き込み専用の属性を定義したいときに使う。

コラム 9.2. 10種類の人々

「この世には10種類の人間がいる。2進法を理解できる奴と、2進法を理解できない奴だ」は、この業界に古くから伝わるジョーク(らしい)です。
ローランドみたいですね(笑)もしかしたらローランドはここからパクったのか??

■三項演算子
以下のif elseコードが

  if boolean?
    var = foo
  else
    var = bar
  end

以下の様に短縮できる

var = boolean? ? foo : bar

三項演算子をメソッドの戻り値として使うこともよくあります。
※true, falseで関数の実行結果を判断するなど。

9.3.2 [Remember me]をテストする

assert_equalは、<期待>, <実際の値>の順で値を並べる。

assert_equal <expected>, <actual>

9.4.1 本章のまとめ

  • Railsではページ遷移の際に「状態」を保持することができる。ページの状態を長時間保持したいときは、cookiesメソッドを使って永続的なセッションにしましょう
  • remember_token と remember_digest をユーザごとに関連付けて、永続的セッションが実現できる
  • cookiesメソッドを使うと、ユーザのブラウザにcookiesなどを保存できる
  • (一般的に)セッションとcookieをそれぞれ削除すると、ユーザのログアウトを実現できる

第10章 ユーザーの更新・表示・削除

10.1 ユーザーを更新する

target="_blank" を使用すると、リンク先を新しいタブ(またはウィンドウ)で開くようになるので、別のWebサイトへリンクするときに便利な要素。
★個人的にリンク先へ飛ぶときは別のタブが嬉しいから、自分が実装するときはこれは絶対実装したいなと思った!
 個人的にはPC用(PCが主なクライアントが想定)のサイトは絶対コレ導入すべきやと思う!

<a href="https://gravatar.com/emails" target="_blank">change</a>

Railsは、form_with(@user) を使ってフォームを構成すると、@user.new_record?true のときにはPOST を、false のときにはPATCH を使います。

10.2.3 フレンドリーフォワーディング

*Tutorialでは@userではなくuserが使われているが、使うとusers_login_testでエラーが発生するので@userを使う。

sessions_controller.rb
  def create
    @user = User.find_by(email: params[:session][:email].downcase)              # paramsハッシュで受け取ったemail値を小文字化し、email属性に渡してUserモデルから同じemailの値のUserを探して、user変数に代入
    if @user && @user.authenticate(params[:session][:password])                 # user変数がデータベースに存在し、なおかつparamsハッシュで受け取ったpassword値と、userのemail値が同じ(パスワードとメールアドレスが同じ値であれば)true
      log_in @user                                                              # sessions_helperのlog_inメソッドを実行し、sessionメソッドのuser_id(ブラウザに一時cookiesとして保存)にidを送る
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)   # ログイン時、sessionのremember_me属性が1(チェックボックスがオン)ならセッションを永続的に、それ以外なら永続的セッションを破棄する
      redirect_back_or @user                                                    # userの前のページもしくはdefaultにリダイレクト
    else
      flash.now[:danger] = 'Invalid email/password combination'                 # flashメッセージを表示し、新しいリクエストが発生した時に消す
      render 'new'                                                              # newビューの出力
    end
  end

*実際のエラー文

Error:
UsersLoginTest#test_login_with_remembering:
NoMethodError: undefined method `remember_token' for nil:NilClass
    test/integration/users_login_test.rb:60:in `block in <class:UsersLoginTest>'

10.5.1 本章のまとめ

  • ユーザーは、編集フォームからPATCHリクエストをupdateアクションに対して送信し、情報を更新する
  • Strong Prameters (params[:foobar])を使うことによって、安全にWeb上から変更させることができる
  • beforeフィルタを使うと、特定のアク4が実行される前にメソッドを呼び出すことができる(※めちゃ便利!!色んな活用用途がありそう!!)
  • Authorization(認可)のテストでは、特定のHTTPリクエストを直接送信する簡単なテストと、ブラウザの操作をシミュレーションする(ユーザが実際にする操作)難度の高いテスト(統合テスト)の2つを実行した(個人的には、統合テストのシミュレーションに必要なテストを考えるのは、論理的に考える必要があって、建設的に思考をこらせて楽しかった。)
  • フレンドリーフォワーディングは実際のアプリを作るときは絶対いるから、覚えて積極的に実装すべき(UX的に基本的にフレンドリーフォワーディングは必須)
  • rails db:seed コマンドは、db/seeds.rb にあるサンプルデータをDBに流し込む
  • render @usersを実行すると、自動的に _user.html.erb パーシャルを参照し、各ユーザーをコレクションとして表示する
  • boolean型のadmin属性をUserモデルに追加すると、admin?という論理オブジェクトを返すメソッドが自動的に追加される

第11章 アカウントの有効化

ユーザActivationの流れ
1. ユーザーの初期状態は「有効化されていない」(unactivated)にしておく。
2. ユーザー登録が行われたときに、有効化トークンと、それに対応する有効化ダイジェストを生成する。
3. 有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく。
4. ユーザーがメールのリンクをクリックしたら、アプリケーションはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する。
5. ユーザーを認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」(activated)に変更する。

11.3.3 有効化のテストとリファクタリング

assigns メソッドを使うと、対応するアクション内のインスタンス変数にアクセスできるようになる。
例えば、Usersコントローラの create アクションでは @user というインスタンス変数があるが、テストで assigns(:user) と書くと、userインスタンス変数にアクセスできるようになる。

testでエラーが出た。。
こちらのteratailの記事を参考に以下のコードを

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      if user.activated?
        log_in user
        params[:session][:remember_me] == '1' ? remember(user) : forget(user)
        redirect_back_or user
      else
        message  = "Account not activated. "
        message += "Check your email for the activation link."
        flash[:warning] = message
        redirect_to root_url
      end
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

以下のようにすると成功しました〜

  def create
    @user = User.find_by(email: params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])
      if @user.activated?
        log_in @user
        params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
        redirect_back_or @user
      else
        message  = "Account not activated. "
        message += "Check your email for the activation link."
        flash[:warning] = message
        redirect_to root_url
      end
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

11.5.1 本章のまとめ

  • アカウント有効化はActive Recordオブジェクトではないが、セッションの場合と同様に、リソースでモデル化できる
  • Railsはメール送信で扱うAction Mailerのアクションとビューを生成できる
  • Action MailerはテキストメールとHTMLメールの両方を利用できる
  • Mailer Actionで定義したインスタンス変数は、他のアクションやビューと同様、Mailerのビューから参照できる
  • アカウント有効化のために、生成したトークンを使って一意のURLを作る
  • SendGridを使うと、production環境からメール送信できる

12章 パスワードの再設定

12.3.3 パスワードの再設定をテストする

・assignsメソッドはコントローラのインスタンス変数をテストするメソッド。
 引数にインスタンス変数をシンボル型で渡す。
 そうすることでインスタンス変数にアクセスできるようになり、テストができる。

    @user = assigns(:user)

12.5.1 本章のまとめ

  • Railsはメール送信で扱うAction Mailerのアクションとビューを生成できる
  • より安全なパスワード再設定のために、ハッシュ化したトークン(ダイジェスト)を使う

第13章ユーザーのマイクロポスト

13.4.1 基本的な画像アップロード

■Active Storage
Active Storageを使うことで画像を簡単に扱うことが出来、画像に関連付けるモデルも自由に指定できます。
Active Storageは汎用性が高く、平文のテキストやPDFファイル、音声ファイルなど様々なバイナリファイルを扱えます。

Active Storage APIの中で最初に知っておく必要があるのはhas_one_attachedメソッドです。これは、指定のモデルと、アップロードされたファイルを関連付けるのに使います。
has_one_attached は指定のモデルとアップロードされたファイルを関連付けるのに使えます。
has_one_attached の場合、「マイクロソフト1件に付き画像は1件」ですが、
has_many_attached を使えば、「マイクロソフト1件に付き複数の画像」を添付できます。

13.5.1 本章のまとめ

  • Rails は複数のキーインデックスをサポートしてる
  • Userは複数のMicropostsを持っていて(has_many)、Micropostは一人のんUserに依存してる(belongs_to)
  • user.microposts.build(...)というコードは引数で与えたユーザに関連付けされたマイクロポストを返す
  • dependent: :destroy オプションを使うと、関連付けされたオブジェクトと自分自身を同時に削除する
  • fixtureは関連付けを使ったオブジェクトの作成もサポートしてる
  • パーシャルを呼び出すときに一緒に変数を渡すことができる
  • where メソッドを使うとActive Recordを通して選択(部分集合を取り出すこと)ができる ※生のSQL文と同じ様な文章で取得できる

第14章ユーザーをフォローする

14.2.2 統計と[Follow]フォーム

@user ||= current_user

上記のコードは @user がnilではない場合は何もせず、nilの場合には @user にcurrent_userを代入するというコード

14.4.3 本章のまとめ

  • has_many :throughを使うと、複雑なデータ関係をモデリングできる
  • has_manyメソッドには、クラス名や外部キーなど、いくつものオプションを渡すことができる
  • 適切なクラス名と外部キーと一緒にhas_many/has_many :throughを使うことで、能動的関係(フォローする)や受動的関係(フォローされる)がモデリングできた
  • ルーティングは、ネストさせて使うことができる
  • whereメソッドを使うと、柔軟で強力なデータベースへの問い合わせが作成できる
  • Railsは(必要に応じて)低級なSQLクエリを呼び出すことができる

番外編

■assertの一覧表

メソッド 説明
assert_template(expected, message = nil) そのアクションで指定されたテンプレートが描写されているかを確認する
assert_not( test, [msg] ) testfalseかを確認する。
assert_select "div.nav" selector(div)に合致した要素の内容を引数equality(nav)でチェック

※assert_selectは柔軟でパワフルな機能で、多くのオプションがあるが、レイアウト内で頻繁に変更されるHTML要素 (リンクなど) をテストするぐらいに抑えておくとよい。

「!!」(「バンバン(bang bang)」と読みます)
オブジェクトをBoolean(論理)値に変換できる演算子。
nilはfalseになります。

>> !!nil
=> false

その他のあらゆるRubyのオブジェクトは、ゼロですらtrueです。

>> !!0
=> true

■! ビックリマーク(感嘆符)について
◆!マークを使うことで、データ(〜〜属性)を直接変更できる。
★!を使わん場合

  before_save { self.email = email.downcase }

★!を使う場合

  before_save { email.downcase! }

◆!をメソッドにつけることで例外を発生させられる!!
create, saveでの例。
! をつけない場合(create save)
・処理に実行して、レコードの作成/保存に失敗して際、 nil が返される。
! をつける場合(create! save!)
例外(例: ActiveRecord::RecordNotFound ERROR )を発生させられる。

■HTMLのtype= “email”
htmlでtype=“email”にすると、携帯電話から入力フォームをタップすると、メールアドレス用に最適化された特別なキーボードが表示される。

private キーワード
- そのファイル(クラス)内でしか使わないメソッドを定義するために使われる
- 他のファイル(クラス)ではprivate内で定義されたメソッドは使用できない。
- 他のファイルでは使われないメソッドをprivateにすることで、想定外のエラーが避けられる

  private

    def user_params
      params.require(:user).permit(:name, :email, :password, :password_confirmation)
    end

PATCH Method for HTTP
- Putメソッドは更新というよりは置換
- Putsで置き換え先が「空」でも、値を置き換える(置き換えようとしてる値をそのまま代入する)
- Patchメソッドは既存のリソースを更新・変更・修正

■<<演算子(Shovel Operator)
<< で配列の最後に追記することができます。

user.following << other_user

さいごに

結構時間かかりました。。Cloud9のエラー等も伴いましたが、結果1ヶ月強くらいかかってしまいました。。
結構ボリューミーだったので仕方ないと思ってますが、もっと早く終わらせてる人もいるので、Portfolio作りは早く終わらせようと思います!!

読んでいただきありがとうございました!!

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

【Ruby on Rails】投稿にタグ付け・インクリメンタルサーチ機能を実装する方法(gemなし)

タグ付けを行う前の前がき

本記事の概要

・投稿にタグ付けをできるようにする。
・文字を入力する度に自動検索を行ってくれる機能(インクリメンタルサーチ)をタグにを実装する

開発環境

Mac OS Catalina 10.15.4
ruby 2.6系
rails 6.0系
※rails newでアプリケーションは作成済みであることを前提としています。

タグ付け機能の完成形イメージ

demo

上図のGifのように、タグを入力し始めるとDBに保存されているタグを元にオススメタグを表示できるようにしています。
今回の記事を元にタグ付け機能を実装できれば、タグ検索なども容易に実装できるかと思います。

タグ付け機能実装の流れ

1. Tag,Post,PostTagRelation、Userモデルを作成
2. 各種モデルのmigrationファイルを編集
3. Formオブジェクトを導入
4. ルーティングの設定
5. postsコントローラーを作成、アクション定義
6. ビューファイルの作成
7. インクリメンタルサーチの実装(JavaScript)

上記の手順で実装を行ってきます。

1.Tag,Post,PostTagRelation,Userモデルを作成

er-figure

まずは、各種モデルを導入しましょう。

%  rails g model tag
%  rails g model post
%  rails g model post_tag_relation
%  rails g devise user

そのまま、導入した各モデルを関連付け(アソシエーション)してバリデーションを記述しましょう。

post.rb
class Post < ApplicationRecord
  has_many :post_tag_relations
  has_many :tags, through: :post_tag_relations
  belongs_to :user
end
tag.rb
class Tag < ApplicationRecord
  has_many :post_tag_relations
  has_many :posts, through: :post_tag_relations

  validates :name, uniqueness: true
end

「through: :中間テーブル」とすることで、多対多の関係であるPostモデルとTagモデルのアソシエーションを組んでいます。
注意点としては、throughによる参照前に中間テーブルの紐付けを行う必要があります。
(コードは上から読み込まれるので、 has_many :posts, through: :post_tag_relations → has_many :post_tag_relationsの順で書いてしまうとエラーになります。)

post_tag_relation
class PostTagRelation < ApplicationRecord
  belongs_to :post
  belongs_to :tag
end
user.rb
class User < ApplicationRecord

  #<省略>
  has_many :posts, dependent: :destroy
  validates :name, presence: true

Userモデルのhas_manyのオプションに、dependent: :destroyと付けているのは、親要素であるユーザー情報が削除された時にそのヒトの投稿も併せて削除されるようにするためです。

なお、PostモデルとTagモデルにて空データを保存させないようにするための記述(validates :〇〇, presence: true)に関しては、後ほど作成するフォームオブジェクトでまとめて指定しますので、今は必要ありません。

2.各種モデルのmigrationファイルを編集

続いて、作成したモデルにカラムを追加していきます。
(最低限必要なのは、tagのnameカラムくらいなので、その他はお好みでアレンジされてください。)

postのマイグレーションファイル
class CreatePosts < ActiveRecord::Migration[6.0]
  def change
    create_table :posts do |t|
      t.string :title, null: false
      t.text :content, null: false
      t.date :date
      t.time :time_first
      t.time :time_end
      t.integer :people
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end

postのマイグレーションファイルで外部キーとしてuserを参照しているのは、後ほどuser名を投稿一覧で表示するためです。

tagのマイグレーションファイル
class CreateTags < ActiveRecord::Migration[6.0]
  def change
    create_table :tags do |t|
      t.string :name, null: false, uniqueness: true
      t.timestamps
    end
  end
end

上記のnameカラムにuniqueness: trueを適用しているのは、タグ名の重複を防ぐために導入しています。
(タグは同じ名前のものが何度も使われることが想定されるので、重複を防いだらタグ付け機能として成り立たなくない?と思われるかもですが、既存のタグを投稿に反映させる方法は後ほど登場します。)

post_tag_relationのマイグレーションファイル
class CreatePostTagRelations < ActiveRecord::Migration[6.0]
  def change
    create_table :post_tag_relations do |t|
      t.references :post, foreign_key: true
      t.references :tag, foreign_key: true
      t.timestamps
    end
  end
end

このpost_tag_relationモデルが、多対多の関係であるpostモデルとtagモデルの中間テーブルの役割を担っています。

userのマイグレーションファイル
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :name,               null: false
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

   #<省略>

ユーザー名を利用したかったので、nameカラムを追加しました。

カラムの編集が終わったら、忘れずに下記コマンドを実行しましょう。

%  rails db:migrate

※まだDBを作成していないという方は、先にrails db:createを実行する必要があります。

3.Formオブジェクトを導入

今回の実装では投稿フォームからpostsテーブルとtagsテーブルへ同時に入力値を保存させたいので、Formオブジェクトを利用します。

まず、appディレクトリの中にformsディレクトリを作り、その中にposts_tag.rbファイルを作成しましょう。
そして、下記のようにpostsテーブルとtagsテーブルに同時に値を保存するためのsaveメソッドを定義します。

posts_tag.rb
class PostsTag

  include ActiveModel::Model
  attr_accessor :title, :content, :date, :time_first, :time_end, :people, :name, :user_id

  with_options presence: true do
    validates :title
    validates :content
    validates :name
  end

  def save
    post = Post.create(title: title, content: content, date: date, time_first: time_first, time_end: time_end, people: people, user_id: user_id)
    tag = Tag.where(name: name).first_or_initialize
    tag.save
    PostTagRelation.create(post_id: post.id, tag_id: tag.id)
  end
end

4. ルーティングの設定

続いて、postsコントローラーのindex・new・createアクションを動かすためのルーティングを設定します。

routes.rb
  resources :posts, only: [:index, :new, :create] do
    collection do
      get 'search'
    end
  end

collection内で定義しているsearchアクションへのルーティングは、インクリメンタルサーチ機能で利用します。

5. postsコントローラーを作成、アクション定義

ターミナルでコントローラを生成します。

% rails g controller posts

生成されたpostsコントローラファイル内のコードは下記のようになります。

posts_controller.rb
class PostsController < ApplicationController
  before_action :authenticate_user!, only: [:new]

  def index
    @posts = Post.all.order(created_at: :desc)
  end

  def new
    @post = PostsTag.new
  end

  def create
    @post = PostsTag.new(posts_params)

    if @post.valid?
      @post.save
      return redirect_to posts_path
    else
      render :new
    end
  end

  def search
    return nil if params[:input] == ""
    tag = Tag.where(['name LIKE ?',  "%#{params[:input]}%"])
    render json: {keyword: tag}
  end

   private

  def posts_params
    params.require(:post).permit(:title, :content, :date, :time_first, :time_end, :people, :name).merge(user_id: current_user.id)
  end
end

createアクションでは、先程Formオブジェクトで定義したsaveメソッドを使ってPostsモデルとTagsテーブルへposts_paramsで受け取った値を保存しています。

searchアクションでは、JS側で取得したデータ(タグ入力フォームで打ち込まれた文字列)を元に、 where + LIKE句でtagsテーブルからデータを引っ張り出し、reder jsonでJSに返しています。(JSファイルは後ほど登場。)

そういう訳なので、↑のsearchアクションは、インクリメンタルサーチを実装しないのであれば必要ありません。

6.ビューファイルの作成

new.html.erb
<%= form_with model: @post, url: posts_path, class: 'registration-main', local: true do |f| %>
  <div class='form-wrap'>
    <div class='form-header'>
      <h2 class='form-header-text'>タイムライン投稿ページ</h2>
    </div>
   <%= render "devise/shared/error_messages", resource: @post %> 

    <div class="post-area">
      <div class="form-text-area">
        <label class="form-text">タイトル</label><br>
        <span class="indispensable">必須</span>
      </div>
      <%= f.text_field :title, class:"post-box" %>
    </div>

    <div class="long-post-area">
      <div class="form-text-area">
        <label class="form-text">概要</label>
        <span class="indispensable">必須</span>
      </div>
      <%= f.text_area :content, class:"input-text" %>
    </div>

    <div class="tag-area">
      <div class="form-text-area">
        <label class="form-text">タグ</label>
        <span class="indispensable">必須</span>
      </div>
      <%= f.text_field :name, class: "text-box", autocomplete: 'off' %>
    </div>
    <div>【おすすめタグ】</div>
    <div id="search-result">
    </div>

    <div class="long-post-area">
      <div class="form-text-area">
        <label class="form-text">イベント日程</label>
        <span class="optional">任意</span>
      </div>
      <div class="schedule-area">
        <div class="date-area">
          <label>日付</label>
          <%= f.date_field :date %>
        </div>
        <div class="time-area">
          <label>開始時刻</label>
          <%= f.time_field :time_first %>
          <label class="end-time">終了時刻</label>
          <%= f.time_field :time_end %>
        </div>
      </div>
    </div>

    <div class="register-btn">
      <%= f.submit "投稿する",class:"register-blue-btn" %>
    </div>

  </div>
<% end %>

僕のアプリ実装で使っていたビューファイルをベタ貼りしているため、コードが冗長になっていますが要はフォームの内容を@post等でルーティングに送れていれば問題ありません。

index.html.erb
<div class="registration-main">
  <div class="form-wrap">
     <div class='form-header'>
      <h2 class='form-header-text'>タイムライン一覧ページ</h2>
    </div>
    <div class="register-btn">
      <%= link_to "タイムライン投稿ページへ移る", new_post_path, class: :register_blue_btn %>
    </div>
    <% @posts.each do |post| %>
    <div class="post-content">
      <div class="post-headline">
        <div class="post-title">
          <span class="under-line"><%= post.title %></span>
        </div>
        <div class="more-list">
          <%= link_to '編集', edit_post_path(post.id), class: "edit-btn" %>
          <%= link_to '削除', post_path(post.id), method: :delete, class: "delete-btn" %>
        </div>
      </div>
      <div class="post-text">
        <p>■概要</p>
        <%= post.content %>
      </div>
      <div class="post-detail">
        <% if post.time_end != nil && post.time_first != nil %>
              <p>■日程</p>
        <div class="post-date">
          <%= post.date %>
          <%= post.time_first.strftime("%H時%M分") %> 〜
          <%= post.time_end.strftime("%H時%M分") %>
        </div>
        <% end %>
        <div class="post-user-tag">
          <div class="post-user">
          <% if post.user_id != nil %>
            ■投稿者: <%= link_to "#{post.user.name}", user_path(post.user_id), class:'user-name' %>
          <% end %>
          </div>
          <div class="post-tag">
            <% post.tags.each do |tag| %>
              #<%= tag.name %>
            <% end %>
          </div>
        </div>
      </div>
    </div>
    <% end %>
  </div>
</div>

こちらも同様に冗長なので、適宜必要なところだけ参照ください...

## 7.インクリメンタルサーチの実装(JavaScript)

こちらは、JSファイルをいじります。

tag.js
if (location.pathname.match("posts/new")){
  window.addEventListener("load", (e) => {
    const inputElement = document.getElementById("post_name");
    inputElement.addEventListener('keyup', (e) => {
      const input = document.getElementById("post_name").value;
      const xhr = new XMLHttpRequest();
      xhr.open("GET", `search/?input=${input}`, true);
      xhr.responseType = "json";
      xhr.send();
      xhr.onload = () => {
        const tagName = xhr.response.keyword;
        const searchResult = document.getElementById('search-result')
        searchResult.innerHTML = ''
        tagName.forEach(function(tag){
          const parentsElement = document.createElement('div');
          const childElement = document.createElement("div");

          parentsElement.setAttribute('id', 'parents')
          childElement.setAttribute('id', tag.id)
          childElement.setAttribute('class', 'child')

          parentsElement.appendChild(childElement)
          childElement.innerHTML = tag.name
          searchResult.appendChild(parentsElement)

          const clickElement = document.getElementById(tag.id);
          clickElement.addEventListener('click', () => {
            document.getElementById("post_name").value = clickElement.textContent;
            clickElement.remove();
          })
        })
      }
    });
  })
};

location.pathname.matchを使って、postsコントローラのnewアクションが発火した時に、コードが読み込まれるようにしています。

JS内のおおまかな処理としては、
①keyupでイベント発火させて、タグフォームの入力値をコントローラーへ送る(xhr.〇〇辺り)
②xhr.onload以下でコントローラーから返ってきた情報を元に、予測タグをフロントに表示させる。
③予測タグがクリックされたら、そのタグがフォームに反映される。

以上で、タグ付け機能の実装とインクリメンタルサーチの実装ができました。
ざっくりとした記事にはなりますが、最後までお読み頂きありがとうございました!

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

【Rails】投稿時間の日本語化

application.rbを修正

以下を追記。
config.i18n.default_locale = :ja
config.time_zone = 'Tokyo'

application.rb
require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module ChatApp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 6.0
    config.i18n.default_locale = :ja
    config.time_zone = 'Tokyo'

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
  end
end

ja.ymlファイルを作成

config/locales/ja.yml
ja:
  time:
    formats:
      default: "%Y/%m/%d %H:%M:%S"

lメソッドを用いて時間を日本化

以下は一例

html.erb
<div class="message-date">
  <!-- 投稿した時刻を出力する -->
  <%= l message.created_at %>
</div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[プログラミング大全] §4 変数と定数

目的

 「プログラミングを学ぼう!!」と意気込んだものの、
プログラミングって難しそうだし、英語も読めないし、PC操作自体苦手だし、
昔に挫折したし、というあなたに向けて、
この記事を読み・PCを動かすだけで「なんだ!プログラミングって面白いじゃん!」
って思ってもらうのが目的です。

プログラミング大全というタイトルで連載化していきたいと考えています。

開発環境

  • Mac:macOS10.15.7(メインPC)
  • ブラウザ:Google Chrome
  • テキストエディタ:VSCode(Visual Studio Code)

変数と定数

 今日は変数と定数について書いていきたいと思います。
中学数学で聞き覚えがあると思いますがプログラミング上ではニュアンスが少し異なります。

変数とは

変数値は、文字列や数値といったオブジェクトを認識するための手法です。
変数を利用することで、再利用したいオブジェクトを変数の名前で呼び出すことが可能です。

言葉だけだとイメージしにくいので実際にプログラムを動かしながら確認していきましょう!

sample.rb
word = "Hello World" #この場合wordが変数名になり"Hello world"が収納したい値になります。
puts word #putsの後に変数名を打ち込みます

ターミナルで確認します。

 $ ruby sample.rb #以下の様に表示されれば成功です。
Hello World

変数の特徴としてはプログラム中に再代入が可能なことです。

sample.rb
word = "Hello World" 
word = "このリンゴは200円です。" #上の行で変数化したものを変更します。
puts word 

ターミナルで確認します。

 $ ruby sample.rb #以下の様に表示されれば成功です。
このリンゴは200円です。

上記の様に後から代入したものに書き変わっていきます。
Rubyにおいては変数にはどんなオブジェクトを代入でき文字列を代入した後に
数値を代入することも可能です。変数にメソッドを使うことも可能です。

変数を扱う練習

イメージしやすくするために、もう少し変数を扱っていきましょう。

「底辺が3、高さが8の三角形の面積」を求めるプログラムを書きます。

samole.rb
bottom = 3 #底辺を変数としています。
puts bottom * 8 / 2

ターミナルで確認します。

12

ではこの三角形の底辺が5になった場合はどうすれば良いでしょうか?

samole.rb
bottom = 5 #ここの値を5に帰るだけです
puts bottom * 8 / 2

ターミナルで確認します。

20

この様に変数を使ってわかりやすいコードを書くことを心がけましょう!

定数

最後に定数ですが、定数は基本的に変数と同様に値を格納する方法です。
変数との違いは再代入が不可です。
定数を定義することは、最初の文字を大文字にづることです。
*慣習としては全て大文字にし、再代入はしません。

定数を再代入しようとすると以下の様にエラー文が出ます。

sample.rb
WORD = "Hello world"
WORD = "このリンゴは200円です。"
puts WORD
$ ruby sample.rb
sample.rb:2: warning: already initialized constant WORD
sample.rb:1: warning: previous definition of WORD was here
このリンゴは200円です。

定数に関しては円周率など変わることがない値に使いましょう!

sample.rb
PI = 3.14
puts 5 * 5 * PI
$ ruby sample.rb #以下の様に表示されれば成功です、
78.5

以上です!

備考

───────────────────────────────
■著者おすすめの本
───────────────────────────────

「プロになるためのWeb技術入門」

「転職の思考法」

「ハイパワーマーケティング」

「嫌われる勇気」

「アウトプット大全」

───────────────────────────────
■著者おすすめの映画
───────────────────────────────

「マイ・インターン」

「シン・ゴジラ」

「ドラゴンボール超 ブロリー」

「School of Roc」

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

【Ruby on Rails】Rubocop-airbnbを使用して、コードチェック

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

RuboCopとは

RuboCopとは、rubyコードが「コーディング規約どおりに書かれているか」をチェックする静的コード解析ツールです。

rubocop−airbnbとは

airbnb社が作成した解析となり、今回はこちらを使用しコードチェックを行います。
gitは下記の通りです。
https://github.com/airbnb/ruby/tree/master/rubocop-airbnb

Rubocop-airbnb」のインストール

下記の通りに追加します。

Gemfile
group :development do

...

  gem 'rubocop-airbnb'
end
ターミナル
$ bundle install

設定ファイルの作成

アプリケーションフォルダ配下に
「.rubocop.yml」と「.rubocop_airbnb.yml」ファイルを作成します。
これらを作成することによりrubocopでコード解析したくないファイルやフォルダを指定します。
※ファイル名の「.」は必要なため忘れず記載してください。

.rubocop.yml
inherit_from:
  - .rubocop_airbnb.yml

AllCops:
  Exclude:
    - 'db/**/*'
    - 'bin/*'
    - 'config/environments/*'
    - 'config/application.rb'
    - 'config/initializers/*'
    - 'config/spring.rb'
    - 'lib/tasks/*'
    - 'vendor/**/*'
    - 'path/ruby'
.rubocop_airbnb.yml
require:
  - rubocop-airbnb

Rubocopでコードのチェック

ターミナルでアプリケーションフォルダに移動し、下記を実行。

ターミナル
$ bundle exec rubocop --require rubocop-airbnb

...

50 files inspected, 150 offenses detected

このように表示されます。
50ファイルをチェックして150箇所のコーディング違反があったことを意味しています。
このように数が膨大になると一つ一つチェックは難しいため、下記を実行。

ターミナル
$ bundle exec rubocop --require rubocop-airbnb -a

...

50 files inspected, 150 offenses detected, 150 offenses corrected

このように150個すべてを修正してくれました。
これでも修正が出来ていない場合は、目視での確認になります。

まとめ

このようにコードの自動修正が簡単にできます。
すごく便利なのでぜひ使ってみてください。

またtwitterではQiitaにはアップしていない技術や考え方もアップしていますので、
よければフォローして頂けると嬉しいです。
詳しくはこちら https://twitter.com/japwork

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

メソッドについて

メソッド

プログラムにおける命令のことをメソッドと呼びます。
メソッドとは、プログラミングにおける何らかの処理をまとめたものです。
メソッド名をプログラム中に記述することで、そのメソッドの処理を実行できます。

メソッド.png

Rubyには便利なメソッドがたくさん用意されています。
たとえば、ターミナルで文字入力モードを起動する、指定した文字を
ターミナルに出力する、などのメソッドです。
また、特定の値にのみ使用できるメソッドもあります。

文字列にも、使用できるメソッドが用意されています。

まとめ

メソッドとは、プログラミングにおける何らかの処理をまとめたもの。

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

文字列の連結方法

文字列の連結

文字列を連結させるには、文字列同士を+(プラス)で繋ぎます。数式と同じような書き方です。

以下の例では、HelloWorldの文字列が繋がっています。

【例】irb
# 文字列を連結
irb(main):001:0> "Hello " + "World"
=> Hello World

irbでコードを実行してみましょう!!

文字列を連結してみましょう。
以下の例のようにirbでコードを実行してみて下さい。

irb
# 文字列を連結
irb(main):001:0> "Good" + " morning"

# 続けてこのように表示されれば成功
=> "Good morning"

そうすることで、上記のように文字列の連結ができます!!

まとめ

irbとは、ターミナルから直接Rubyのプログラムを動かすことができる機能のこと。
文字列とは、プログラミングの中で文字を扱うための値のこと。
文字列は、+(プラス)で連結できる。

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

【備忘録】MVCモデル まとめ

はじめに

筆者の漁った色々な知識を備忘録としてまとめ、残しています。

MVCとは

モデル(Model)頭文字「M」、ビュー(View)頭文字「V」、コントローラー(Controller)頭文字「C」の略であり、アプリケーション設定を整理するための概念。イメージはユーザーがブラウザで入力(クリック)した内容をWebアプリケーションで処理する流れ。

RubyonRailsは、MVCモデルで構成されている。

Model(モデル)

アプリケーション固有のデータを扱う部分です。Controllerからの依頼を受けて処理する(Modelに直接処理を記入することもある(例:「特定の文字列を探して抽出して」とか、「データベースのこの項目には空白入れないで」 等))。データベースに対して、データの登録や取得、更新、削除などの処理を行う。

View(ビュー)

PCの画面に関わる部分。データベースの情報を表示する場合、Controllerから情報を受け取り、ブラウザに表示させるHTMLを実際に組み立てる。

Controller(コントローラ)

ModelやViewを制御する部分です。ユーザーからのリクエスト(例:TOPから商品一覧(データベースに登録されてる商品)をみたい 等)を受けて、Modelと連携したり、どのView(画面)を表示するのかといったことを制御します。

RubyonRailsでは

プログラムの構造をMVCの役割によって分けることで、プログラムのメンテナンス性を向上させたり、複数人で開発するときに影響する箇所を限定できるようになっています。

抽象化すると

Viewが営業
Controllerが事業推進
Modelが事務

Veiw(商品一覧)を表示する時
View     :問い合わせがあったので、Controllerさんお客さんに見せるプレゼン用のカタログください!
Controller  :了解!Viewさん! Modelさん!キャビネットからもってきて!
Model     :controllerさん、了解しました。今とってきます。

Viewでユーザーが新規会員登録された時
View     :Controllerさんお客さんから依頼とってきました!
Controller  :ありがとう!Viewさん! Modelさんに渡しとくね!
Model     :controllerさん、了解しました。記録残しておきますね。

そして各役割ごとに、業務をルーチン化して効率を向上させている。
※viewとcontrollerには、実は課長(application_controller.rbとapplication_html.erb))がいて、課(フォルダ)内にまとめて命令を出せたりする。

みたいなイメージを持っています。

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

【備忘録】MVCモデル 簡単なまとめ

はじめに

筆者の漁った色々な知識を備忘録としてまとめ、残しています。

MVCとは

モデル(Model)頭文字「M」、ビュー(View)頭文字「V」、コントローラー(Controller)頭文字「C」の略であり、アプリケーション設定を整理するための概念。イメージはユーザーがブラウザで入力(クリック)した内容をWebアプリケーションで処理する流れ。

RubyonRailsは、MVCモデルで構成されている。

Model(モデル)

アプリケーション固有のデータを扱う部分です。Controllerからの依頼を受けて処理する(Modelに直接処理を記入することもある(例:「特定の文字列を探して抽出して」とか、「データベースのこの項目には空白入れないで」 等))。データベースに対して、データの登録や取得、更新、削除などの処理を行う。

View(ビュー)

PCの画面に関わる部分。データベースの情報を表示する場合、Controllerから情報を受け取り、ブラウザに表示させるHTMLを実際に組み立てる。

Controller(コントローラ)

ModelやViewを制御する部分です。ユーザーからのリクエスト(例:TOPから商品一覧(データベースに登録されてる商品)をみたい 等)を受けて、Modelと連携したり、どのView(画面)を表示するのかといったことを制御します。

RubyonRailsでは

プログラムの構造をMVCの役割によって分けることで、プログラムのメンテナンス性を向上させたり、複数人で開発するときに影響する箇所を限定できるようになっています。

抽象化すると

Viewが営業
Controllerが事業推進
Modelが事務

Veiw(商品一覧)を表示する時
View     :問い合わせがあったので、Controllerさんお客さんに見せるプレゼン用のカタログください!
Controller  :了解!Viewさん! Modelさん!キャビネットからもってきて!
Model     :controllerさん、了解しました。今とってきます。

Viewでユーザーが新規会員登録された時
View     :Controllerさんお客さんから依頼とってきました!
Controller  :ありがとう!Viewさん! Modelさんに渡しとくね!
Model     :controllerさん、了解しました。記録残しておきますね。

そして各役割ごとに、業務をルーチン化して効率を向上させている。
※viewとcontrollerには、実は課長(application_controller.rbとapplication_html.erb))がいて、課(フォルダ)内にまとめて命令を出せたりする。

みたいなイメージを持っています。

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

【注意!!】Railsのdeviseとviewファイルをhamlに変換する時の注意点

こんばんは
アロハな男、やすのりです!

今日はdeviseのビューファイルを修正している時に遭遇した躓きポイントについてお話していこうと思います。

ただ、前提が当てはまる人は参考にしていってね!!

前提

Railsでアプリケーション作成中にGemの
deviseを使用していて、更にhaml-rails等でhtml.erbhtml.hamlに変換している人

状況

ファイル名

deviseの新規登録ページのビューファイルに名前の入力欄を追加したいと思いコード修正していました。

views/users/registrations/new.html.haml
%h2 Sign up
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
  = render "users/shared/error_messages", resource: resource
  .field
    = f.label :name
    %br/
    = f.text_field :name, autofocus: true
  .field
    = f.label :email
    %br/
    = f.email_field :email, autofocus: true, autocomplete: "email"
  .field
    = f.label :password
    - if @minimum_password_length
      %em
        (#{@minimum_password_length} characters minimum)
    %br/
    = f.password_field :password, autocomplete: "new-password"
  .field
    = f.label :password_confirmation
    %br/
    = f.password_field :password_confirmation, autocomplete: "new-password"
  .actions
    = f.submit "Sign up"
= render "users/shared/links"

ということで、:nameの欄を足してこれでバッチリだ!!
と、思っていたら...
ファイル名

...え?
何も変わってなくない??あれ???

修正するビューファイル間違えたかな?と思って確認してみても間違いなく registrations って書いてあるし...

ファイル名
あれ!?
なぜか、new.html.erbを参照してる!?
しかも参照先がapp/views/deviseregistrations/new.html.erbになってるぞ!?

と、思いいろいろ調べて見ると原因が見えてきました。

原因

僕のアプリケーションの構成上、deviseにUserモデルを持たせていたので、deviseのビューファイルを生成する時に$ rails g devise:views usersとコマンドを打っていたんですが、これが原因みたいです。
そもそもdeviseのモデルがUserモデル以外に無いのであれば、$ rails g devise:viewsだけで良かったみたいです。
※Userモデル以外というよりは、deviseのモデルが1つだけの時と言う方が正しいかも?

2つのコマンドの違いは?

生成されるビューファイルのディレクトリ構造が少し変わってしまいます。
具体的には$ rails g devise:views usersとコマンドを打った場合は、

app/views/users/以下にディレクトリとビューファイルが生成されます。

一方、$ rails g devise:viewsとコマンドを打った場合は

app/views/devise 以下にディレクトリとビューファイルが生成されます。

deviseディレクトリ...

ファイル名

これだぁぁあああ!!

はい、ということでビューファイルを生成した段階でコミットしていたので、コミット履歴を削除してコマンドを打ち直した後に、新規ビューファイルのコードを修正してみました。

すると...

ファイル名

よしっ!!
名前の入力欄が加わってるぞ!!

ファイル名

参照先もちゃんとnew.html.hamlになってる!!

結論

deviseのビューファイルを生成する時は、基本$ rails g devise:viewsでOK!!

皆さんも気をつけてね!!

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

【Ruby】twitter-adsの中身を叩き切る

業務でtwitter-adsを使ってTwitterの広告配信管理画面のAPIを叩くことがあったのですが、
Ruby初心者過ぎてかなりしんどかったのでメモしておきます。

twitter-adsバージョンは7です。

クライアントを生成する

これを作らないことには始まりません。

require 'twitter-ads'

# initialize the client
client = TwitterAds::Client.new(
  CONSUMER_KEY,
  CONSUMER_SECRET,
  ACCESS_TOKEN,
  ACCESS_TOKEN_SECRET
)

アカウントを取得する

githubのquick startをみると。

# load the advertiser account instance
account = client.accounts('c3won9gy')

とありますが、そこに渡すID?はなんやねん。という話になります。
色々と調べていった結果、

client.accounts.each do |v|
  p v.id
end

こういう感じでやったらそれらしいのが1件でてきたのでそこででてきたIDを渡してあげればいけました。

アカウントが保持しているキャンペーン一覧をだす

上記で取得した、accountを使います。

account.campaigns.each do |v|
  p "====================="
  p v.id
  p v.reasons_not_servable
  p v.servable
  p v.deleted
  p v.created_at
  p v.updated_at
  p v.name
  p v.funding_instrument_id
  p v.end_time
  p v.start_time
  p v.entity_status
  p v.currency
  p v.standard_delivery
  p v.daily_budget_amount_local_micro
  p v.total_budget_amount_local_micro
  p v.to_delete
  p "====================="
end

こうやってやるとズラーっとキャンペーン一覧がでてきます。
(こうやって一個一個pせずに一行で全部の値を出す方法ご存じの方いたら教えて下さい・・・)

※ステータスが「削除済み」となっているキャンペーンについては出力されません。

特定のキャンペーンのみのオブジェクトを作りたい場合は、

campaign = account.campaigns('1m5bbbb')

こういう感じで生成してあげて、(渡すIDは上記のv.idで出てきた値です)

campaign.name

という感じでキャンペーン名が取得できます。

campaign.name = "MADOKA"
campaign.save

ってやればキャンペーン名を変更できたりもします。幸せいっぱいですね。

広告グループを出力する

account.line_items()

こちらもaccountが保持する広告グループを出力するので、accountを使います。

これで広告グループの一覧が取得できます。
※ステータスが、「実行中」、「停止中」、「予約済み」、「おすすめ」のものがでてきます。

テイラードオーディエンス一覧をだす。

これもaccountを使います。

account.tailored_audiences.each do |v|
  p "==========="
  p v.id
  p v.name
  p v.list_type
  p v.audience_size
  p v.audience_type
  p v.metadata
  p v.partner_source
  p v.reasons_not_targetable
  p v.targetable
  p v.targetable_types
  p "==========="
end

ここまででなんとなく取得系の雰囲気はつかめてきました。

ここから更に実践的な使い方を書いていきます。

広告グループに紐付いているテイラードオーディエンスを出す。

広告グループは、

line_item = account.line_items('1abcd')

で取得できます。

で、この取得したline_itemで、

line_item.targeting_criteria.each do |target|
  if target.targeting_type == 'TAILORED_AUDIENCE' then
    p account.tailored_audiences(target.targeting_value).name
  end
end

こんな感じになります。

広告グループに紐付いているテイラードオーディエンスの紐づきを解除する

line_item.targeting_criteria.each do |target|
  if target.targeting_type == 'TAILORED_AUDIENCE' then
    target.delete!
  end
end

特定のテイラードオーディエンスを指定した広告グループに紐付ける

これがかなり手こずったのですが、

tc = TwitterAds::TargetingCriteria.new(account)
tc.line_item_id    = '紐付け対象広告グループのID'
tc.targeting_value = '紐付けたいテイラードオーディエンスID(value)'
tc.targeting_type  = 'TAILORED_AUDIENCE' ここは固定でこれ入れればOK
tc.save

といった感じでした。

以上です

紐付けに関して。
TA(テイラードオーディエンス)を作成してもすぐには「準備完了」ステータスにならないので、
作成後、定期的にそのTAの様子をバッチで見に行って、ステータスが「準備完了」(targetable)になったのを確認して、紐付けっていうロジックを組む必要があります。

このSDKの使い方を理解するまでがくそしんどかったのですが、理解さえできれば後は書くだけなので楽しかったです。

今日はそんな感じです。

https://github.com/twitterdev/twitter-ruby-ads-sdk

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

【エラー】Mysql2::Error: Specified key was too long; max key length is 767 bytes

エラーの忘備録

エラー内容

rails db:migrateをした時に、以下のエラー文が出ました。(Mysqlのエラー)

Mysql2::Error: Specified key was too long; max key length is 767 bytes

指定したキーが長すぎるというエラーです。
テーブルに色々カラムを追加すると、キーの制限である767バイトを超えてしまうため
このエラーが起こってしまいます。

原因

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

encoding: utf8mb4という記述のためエラーが出ます。

解決方法

encoding: utf8に書き換えます。

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

utf8mb4とutf8の違いはいまいちわかっていないのですが、utf8mb4は1〜4バイト、utf8は1〜3バイトの文字が格納できるようです。そのためutf8だと、絵文字などは保存できませんが、代わりに容量が大きくなるのではないかな?と推測しました。

すでにrails db:createでデータベースを作成してしまっている場合は、

% rails db:drop
% rails db:create

% rails db:reset

で、もう一度データベースを作り直してあげる必要があります。

おわりに

もし間違えている点などございましたら、ご指摘いただけましたら幸いです。
読んでいただきありがとうございました!

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

【Rails】アソシエーション

中間テーブル

データ型:referencesとforeign_keyをセットで使用する。
referencesはPKを参照する。よって、:userと記述しているがカラム名はuser_idとなる。
foreign_keyで、他テーブルの情報を参照できる。

model.rb
class CreateRoomUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :room_users do |t|
      t.references :user, null: false, foreign_key: true
      t.references :room, null: false, foreign_key: true
      t.timestamps
    end
  end
end

dependent: :destroy

親子関係のデータについて、親を削除した際に子も削除されるようにする。

親モデルのhas_manyにdependent: :destroyを追記。

(例)親モデル:user、子モデル:post

class User < ApplicationRecord
  has_many :posts, dependent: :destroy
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[エラー]background-image: image-urlでSassC::SyntaxError

background-image: image-urlでSassC::SyntaxError

デプロイ前にcssに記述したbackground-imageを
下記のように記述したら SassC::SyntaxError となりました。

background-image: image-url("IMG_4335 (1).JPG");

原因は文字間にスペースがあったからでした。
初歩的なミスに随分時間を費やしてしまいました。。

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

Rails 5 コードリーディング 第2回 ~ActionView編~

    class RenderedTemplate # :nodoc:
      attr_reader :body, :template

      def initialize(body, template)
        @body = body
        @template = template
      end

      def format
        template.format
      end

      EMPTY_SPACER = Struct.new(:body).new
    end

formatメソッド内の templateが@templateとインスタンス変数でないのはなぜか?

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

heroku コマンドが使えなくなったときのメモ

ある日bash: heroku: command not foundでherokuコマンドが使えなくなりました。
git push heroku masterは使えました。
heroku open やheroku rake db:migrate が使えなかったです。

npm install -g heroku

したら治りました。

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

Railsで申し込み機能を実装する

Railsを用いた投稿の申し込み機能とキャンセル機能の実装方法について解説します。
※ユーザー機能と投稿機能は実装済みであることを前提とします。

実装の流れ

大まかな流れとして、以下の手順で実装していきます。
1. 投稿と申込者の中間テーブルを作成する
2. モデル同士の関連付け(アソシエーション)を行う
3. ルーティングを定義する
4. コントローラーに申し込み機能とキャンセル機能を定義する
5. 申込ボタンのビューを作成する

投稿と申込者の中間テーブルを作成する

投稿は複数人の申込者を持つことができ、ユーザーは複数の投稿に申し込むことができます。
そのため、投稿とユーザーは多対多の関係となり、中間テーブルが必要となります。

また、ここでは同じユーザーの中でも投稿者と申込者を区別するために、申込者をtakerと定義することにします。
投稿のモデル名をPostとすると、post_takersという中間テーブルを作れば良いということになります。

中間テーブルを作成するにはまず、ターミナルに以下のコマンドを打ちモデルを作成します。

$ rails g model PostTaker

出来上がったマイグレーションファイルを開き、以下のように記述します。

2020XXXXXXXXXX_create_post_takers.rb
class CreatePostTakers < ActiveRecord::Migration[6.0]
  def change
    create_table :post_takers do |t|
      t.references :post, foreign_key: true
      t.references :taker, foreign_key: {to_table: :users} 
      t.timestamps
    end
  end
end

ここでのポイントは、t.references :taker, foreign_key: {to_table: :users}の部分です。
このように書くと、中間テーブルの外部キーとして申込者であるtakerという名前を定義しつつ、usersテーブルを参照できます。
参考記事:https://qiita.com/publichtml/items/1fba15d8071fab66d043

モデル同士の関連付け(アソシエーション)を行う

次に、モデルにアソシエーションを記述します。

user.rb
class User < ApplicationRecord
  has_many :post_takers, foreign_key: "taker_id", dependent: :destroy
end
post.rb
class Post < ApplicationRecord
  has_many :post_takers, dependent: :destroy
  has_many :takers, through: :post_takers, dependent: :destroy
end
post_taker.rb
class PostTaker < ApplicationRecord
  belongs_to :taker, class_name: 'User', foreign_key: 'taker_id'
  validates_uniqueness_of :post_id, scope: :taker_id
end

post_taker.rbのbelongs_to :taker, class_name: 'User', foreign_key: 'taker_id'は、
このような書き方をすることでUserモデルに紐付いた申込者と関連付けができます。
参考記事:https://qiita.com/gyu_outputs/items/421cc1cd2eb5b39e20ad

また、validates_uniqueness_of :post_id, scope: :taker_idは、
投稿と申込者の同じ組み合わせが2つ以上登録されないようにするため、このようなバリデーションを記述しています。

ルーティングを定義する

申し込むというアクションをtake、キャンセルというアクションをcancelとしてルーティングに以下のように記述します。

routes.rb
Rails.application.routes.draw do
  resources :posts do
    member do
      get 'take'
      get 'cancel'
    end
  end
end

上記では、パスに投稿のidを含めるためmemberを使っています。
参考記事:https://qiita.com/hirokihello/items/fa82863ab10a3052d2ff

コントローラーに申し込み機能とキャンセル機能を定義する

コントローラーに以下の記述を追加します。
※ここでは投稿詳細ページに申込ボタンを表示させることを前提として書いていますが、各々の状況に合わせて書き換えてください。

posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post

  def show
    @user = @post.user
  end

  def take
    # 該当の投稿とログイン中のユーザーとの中間テーブルのレコードを作成する
    PostTaker.create(post_id: @post.id, taker_id: current_user.id)
    # フラッシュメッセージを表示(フラッシュメッセージを表示させない場合は書かなくて大丈夫です)
    flash[:notice] = '申し込みが完了しました。'
    # 投稿の詳細ページにリダイレクト
    redirect_to action: "show"
  end

  def cancel
    # 中間テーブルの中から、該当の投稿とログイン中のユーザーによるレコードを抽出する
    post_taker = PostTaker.find_by(post_g_id: @post.id, taker_id: current_user.id)
    post_taker.destroy
    flash[:notice] = 'キャンセルが完了しました。'
    redirect_to action: "show"
  end

  private

  def set_post
    @post = Post.find(params[:id])
  end
end

申込ボタンのビューを作成する

最後に、申込ボタンのビューを作成したら完成です!
以下のコードはhamlで記述しています。

show.html.haml
-# 投稿者とログイン中のユーザーが一致しない場合のみボタンを表示させる
- if @user.id != current_user.id
  -# 該当の投稿の申込者の中に、ログイン中のユーザーが含まれているか否かで場合分けする
  - if @post.taker_ids.include?(current_user.id)
    = link_to "申し込みをキャンセルする", cancel_posts_path(@post.id), data: { confirm: "こちらの投稿の申し込みをキャンセルしますか?" }
  - else
    = link_to "申し込む", take_posts_path(@post.id), data: { confirm: "こちらの投稿に申し込みますか?" }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 5 コードリーディング 第一回 ~ActiveRecord newメソッド編~

Railsの使い方はなんとかわかるにようなりました。

でも、コーディング力が足りない。

他の言語に行った時に、応用力がつかないのではないか、、、

と思ったので、

Railsのソースコードを読んでみることにした。

こちらを参考にしてみました。
Railsコードを読んでみた

Active record 周りを読んでみることに
まずは、shift command f で
def new を
全文検索

# frozen_string_literal: true

module ActiveRecord
  # = Active Record \Relation
  class Relation
    MULTI_VALUE_METHODS  = [:includes, :eager_load, :preload, :select, :group,
                            :order, :joins, :left_outer_joins, :references,
                            :extending, :unscope, :optimizer_hints, :annotate]

    SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :strict_loading,
                            :reverse_order, :distinct, :create_with, :skip_query_cache]

    CLAUSE_METHODS = [:where, :having, :from]
    INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having]

    VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS

    include Enumerable
    include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation

    attr_reader :table, :klass, :loaded, :predicate_builder
    attr_accessor :skip_preloading_value
    alias :model :klass
    alias :loaded? :loaded
    alias :locked? :lock_value

(と中略)

    # Initializes new record from relation while maintaining the current
    # scope.
    #
    # Expects arguments in the same format as {ActiveRecord::Base.new}[rdoc-ref:Core.new].
    #
    #   users = User.where(name: 'DHH')
    #   user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
    #
    # You can also pass a block to new with the new record as argument:
    #
    #   user = users.new { |user| user.name = 'Oscar' }
    #   user.name # => Oscar
    def new(attributes = nil, &block)
      block = _deprecated_scope_block("new", &block)
      scoping { klass.new(attributes, &block) }
    end

    alias build new

(途中略)

def _deprecated_spawn(name)
        spawn.tap { |scope| scope._deprecated_scope_source = name }
      end

      def _deprecated_scope_block(name, &block)
        -> record do
          klass.current_scope = _deprecated_spawn(name)
          yield record if block_given?
        end
      end

    def new(attributes = nil, &block)
      block = _deprecated_scope_block("new", &block)
      scoping { klass.new(attributes, &block) }
    end

    alias build new

まずは漠然と理解する

1.
新しいものレコードを作るから attribute(属性)はnil
2.
ブロック引数を渡しているが、ブロックはどこにあるのか?
3.
_deprecated_scope_block メソッドが下に定義されているが、意味がわからない
4.
klassとは何か?

疑問は尽きない
もうちょっとさぐってみる

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

[Rails]rails-i18nを用いた日本語化

はじめに

rails-i18nというgemを用いることでエラーメッセージなどを日本語化することができます。

目次

  1. 言語を日本語に設定する
  2. rails-i18nのインストール
  3. 日本語に設定するためのファイルを作成

1. 言語を日本語に設定する

application.rbを編集します。

:jaとすることで日本語に設定することができます。

config/application.rb
module CosmeticReview
  class Application < Rails::Application
    config.load_defaults 6.0
    #以下の記述を追記します
    config.i18n.default_locale = :ja
  end
end

2. rails-i18nのインストール

gemfileに以下を追記します。
その後、コマンドでbundle installを実行します。

gemfile
gem 'rails-i18n'

3. 日本語に設定するためのファイルを作成

localファイルを作成し、その中に日本語へ翻訳するためのYAMLファイルを作成します。
今回はモデルのエラーメッセージを日本語化しました。

locales/ja.yml
ja:
 activerecord:
   attributes:
     user:
       nickname: ニックネーム
       gender: 性別
       introduce: 自己紹介
       birthday: 生年月日 
     post:
       name: 商品名
       description: 商品の説明
       image: 画像
       shop_name: 購入店
       evaluation: 商品の評価
       price: 購入価格
       category_id: カテゴリー
   time:
     formats:
       default: "%Y/%m/%d %H:%M:%S"

階層が少しでもずれているとエラーが出るので気をつけましょう。

参考リンク

https://github.com/svenfuchs/rails-i18n

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

Ruby アンパサンドを用いた安全参照演算子(&.)とは

はじめに

変数がnilの時、メソッドによってはエラーを起こすものがあったりします。

例えば、なんかの処理で値を配列に詰めたものをeachで回したりするときに、もしnilが入ってたりすると、処理が止まってエラーが出る。。なんてことが起こると困りますよね。

そんなときはガード節(関数や繰り返し処理の最初に例外を取り除くように実装する方法)を使ってnext if [変数].nil?とやって、nilを回避できたりするんですが、論理演算子を応用した別の方法があるときいて、メモしていきたいと思います。

アンパサンド(&)を用いた安全参照演算子

最初に結論から申しますと、[オブジェクト]&.[メソッド]というかたちで書きます。

例えば、

#&を使わないと
ary = [[1,2,3],nil,[5,6]]

ary.each do |element_ary|
  p element_ary.length
end

#=>

#3
#Traceback (most recent call last):
#   2: from test.rb:3:in `<main>'
#   1: from test.rb:3:in `each'
#test.rb:4:in `block in <main>': undefined method `length' for #nil:NilClass (NoMethodError)

#&を使うと
ary = [[1,2,3],nil,[5,6]]

ary.each do |element_ary|
  puts element_ary&.length
end

#=>

#3
#nil
#2

このように、エラーがおきません。

原理

実はこれ、論理演算子&&が関係しています。

一般的に、論理演算子は以下の特徴を持ってます。

・左から順に評価される
・論理式の真偽が決まると、残りの式は評価されない
・最後に評価された式の値が論理全体の値となる

つまり、[条件1] && [条件2]とあったとき、[条件2][条件1]が真のときにのみ、評価されます。[条件1]nilもしくはfalseであった場合、条件2を評価するまでもなく、論理演算子&&の結果はnilもしくはfalseになるからです。

これを応用して、上記の例を用いて説明すると、以下のように書けます。

ary = [[1,2,3],nil,[5,6]]

ary.each do |element_ary|
  p element_ary && element_ary.length
end

element_arynilが入ると[条件1]の時点で、論理演算子の処理が終わり、nilを返すので、[条件2]lengthメソッドにnilが渡ることはありません。

これを省略して以下のように、オブジェクトの後に&をつけることでも表現できるのが、安全参照演算子と呼ばれるものだったのです。

ary = [[1,2,3],nil,[5,6]]

ary.each do |element_ary|
  p element_ary&.length
end

おわりに

論理演算子の応用例ということでしたが、あたまいいなあと思いました。

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

vagrant上で作成したポートフォリオをdockerの開発環境に移行してみた

はじめに

初めまして、初投稿になりますので自己紹介と記事のきっかけについて書かせていただきます。

  • 自己紹介
    2020年9月にDMMWEBCAMPを卒業し、現在転職活動中のエンジニアです。

  • 記事のきっかけ
    他のプログラミングスクールが同じかはわかりませんが、スクール生で共通の開発環境にするために全てvagrant上で仮想環境を構築しカリキュラムとポートフォリオの作成を行っていました。そのため、0からの自力で環境構築を行ったことがなく実務を意識するのならDockerぐらいは自力で入れておきたいと思い学習し始めたのがきっかけです。
    まだまだ勉強中のため、正直かなり怪しいところは多いとは思いますが同じような勉強中の方の参考に少しでもなれば嬉しいです!

この記事で扱うこと

  • Dockerを使用した開発環境の構築
    Vagrant上でできていたことがDocker上でもできるようにするイメージです

  • Dockerfile、docker-composeなどの表記
    参考までに、Vagrantを使用していた時の開発環境は下記になります

    • OS:macOS Catalina 10.15.4
    • バックエンド: Ruby 2.5.7、Rails 5.2.4.3
    • 開発環境: Vagrant 2.2.4、SQLite (※MySQLやPostgreSQLの場合、この後の見本の表記が異なります)
    • 本番環境: MySQL2、AWS (EC2、RDS for MySQL、Route53)、Nginx、 Puma、Capistrano

スクリーンショット 2020-09-13 12.40.05.png

  • 0からの開発環境の構築で起こりうるエラーと解決した方法
    記事のきっかけにもあるように、ローカル環境の構築自体も初めてのためdocker以前のエラーもいくつかありました。解決のために参考にさせていただいた記事をメインに紹介していきたいと思います。

この記事で扱わないこと

  • Dockerfile、docker-composeなどの基礎知識

    Dockerに関する記事やyoutube多くありますがほとんどがコピペでなんとなく進んでしまうので、最初は自分でドキュメントを読みながら手を動かして全体の仕組みをなんとなく先に掴んでおくのが理解しやすいと思いました。
    参考までに自分は下記サイトで学習を始めました。各用語調べながらでも半日〜2日ほどでできる内容かつDockerのイメージが掴めるかと思いますのでおすすめです。

事前準備

$ docker version
Client: Docker Engine - Community
 Cloud integration  0.1.18
 Version:           19.03.13
 API version:       1.40
  • ポートフォリオ(Dockerの開発環境にしたいプロジェクト)をgit cloneしてくる
    記事のきっかけにもありますよう、既にポートフォリオを作成している段階のため環境構築したいものをgithubからローカル上に持ってきましょう
$ mkdir docker
 # Desktopからもしくはvagrantを使用していた時のworkディレクトリなどで実行
$ git clone git@github.com:ユーザー名/リポジトリ名.git

各ファイルの準備

Dockerとプロジェクトの準備ができたので、環境構築に必要なファイルを作成していきます

①Dockerfile

# 先ほどクローンしたディレクトリ上で
$ touch Dockerfile

下記内容を記入していきましょう

# 元となるdocker imageを指定。ポートフォリオのrubyのバージョンと合わせましょう
FROM ruby:2.5.7

RUN apt-get update -qq && apt-get install -y nodejs postgresql-client

RUN apt-get update && apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/*
#sqlite3を指定
RUN apt-get update && apt-get install -y sqlite3 --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

# コンテナ起動した時に、コンテナ内でmyappディレクトリを作成
RUN mkdir /myapp

# コンテナ内の作業ディレクトリの指定(上のRUNで作成したディレクトリ)
WORKDIR /myapp

# ホストマシンのgemfileをコピーし、コンテナ内のディレクトリに貼り付け
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock

# 貼り付けたgemfileを読み込み。bundlerバージョンが2以降だとこれが必要
ENV BUNDLER_VERSION 2.1.4
RUN gem install bundler
RUN bundle install

# アプリケーション(カレントディレクトリ)をコピーして、コンテナ内のディレクトリに貼り付け
COPY . /myapp

# ポートを3000で公開
EXPOSE 3000

# コンテナにてコマンドを実行
CMD ["rails", "server", "-b", "0.0.0.0", "-p", "3000"]
  • ENV BUNDLER_VERSION 2.1.4について
    Gefile.lockの一番下にこのような表記があるかと思いますので確認してみて下さい
Gemfile.lock
BUNDLED WITH
   2.1.4

②docker-compose.yml

# 同じディレクトリ上で
$ touch docker-compose.yml

下記内容を記入していきましょう

docker-compose.yml
version: '3'
services:
  web:
    build:
      context: ./
      dockerfile: Dockerfile 
    # 変更を保存するファイルを指定
    volumes:
      - .:/myapp
    # ポートを指定
    ports:
      - "3000:3000"
    # コンテナの起動を維持させる
    tty: true
    stdin_open: true

MySQLやPostgreSQLを使用している場合だと、web+DBの記述も必要なのですがSQLiteの場合はこちらのみで大丈夫みたいです。まだこの辺りの知識が乏しいため、ゆくゆくはMySQLに変更したいと思っております・・・

コンテナを起動してみましょう

# Dockerfileを元にdocker imageの作成
$ docker-compose build 

# docker container をバックグランドで起動
$ dokcer-compose up -d

# コンテナ内にてmigrationを更新
docker-compose run web rails db:migrate

これで、いつものlocalhost:3000で表示ができればひと段落です!

引っかかったエラーと解決した方法

大体ですが引っかかった時系列順に記載していきます。最初にも記載しておりますが、エラー内容とおそらくの原因の考察がサブで、メインが解決参考にさせていただいた記事の紹介になります。ありがとうございました

①ActiveRecord::PendingMigrationError Migrations are pending

「dokcer-compose up -d で動いているのにっ・・・!」

$ docker-compose up -d                                                  
Creating network "~~~_default" with the default driver
Creating ~~~_web_1 ... done

localhost:3000のページへアクセス時に起きたエラーです
コンテナの起動後、docker-compose run web rails db:migrate を行いましょう、あるあるな気がします
参考:docker-compose upで立ち上げたRailsでActiveRecord::PendingMigrationError Migrations are pending

②To install the missing version, run 'gem install bundler:2.1.4'

「bundleで始まるコマンド(デプロイ・Rspecなど)ができないっ・・・!」

Traceback (most recent call last):
    2: from 
    1: from 
~
Could not find 'bundler' (2.1.4) required by your /Users/ユーザー名/ディレクトリ名/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.1.4`

Dockerfileを作成した時に確認した、Gemfile.lockの「BUNDLED WITH 2.1.4」がローカル上で見つからないイメージです。
参考:To install the missing version, run gem install bundler:2.1.4と出たときの対処法

エラー文の通りにインストールを試してみましょう、ダメだった場合は③へ

gem install bundler -v 2.1.4

③ERROR: While executing gem ... (Gem::FilePermissionError)

ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /Library/Ruby/Gems/2.3.0 directory.

「gemのインストールの権限がありませんっ・・・!」
sudoを頭につけて行うという記事も多かったですが、そのままインストールよりも「rbenv」というバージョン管理のツールを用いて行う方法の方が今後のことを考えるといい気がします。

参考1:gem installでpermissionエラーになった時の対応方法
参考2:rbenvによるRubyのインストールからHello, World!まで

rbenvの用意ができたら、もう一度やってみましょう

gem install bundler -v 2.1.4

④bundle installができない

「bundleが・・・まだダメっ・・・!」

gem installができたところで、デプロイなどの「bundle~」系を入力するとまだエラー文がでます

Install missing gem executables with `bundle install`

エラー文に従って、bundle installをしてみると

# 長いため、最後の部分だけ抜粋
An error occurred while installing mysql2 (0.5.3), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  mysql2

mysql周辺のエラーですが、原因は自力ではわかりませんでした。
下記の参考にさせていただいた記事通りで私は解決できました、本当にありがたい。

参考:【Rails】MySQL2がbundle installできない時の対応方法

これでデプロイはできるようになると思います。

⑤Could not find a JavaScript runtime ~

「動いているけどっ・・・!」

Rspecを走らせた時のエラーです

$ bundle exec rspec spec/ --format documentation

An error occurred while loading ./spec/models/cart_item_spec.rb.
Failure/Error: require File.expand_path('../config/environment', __dir__)

ExecJS::RuntimeUnavailable:
  Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes.

githubに飛んでみると、ExecJSのどれかをダウンロードするよう勧めています。
gemをいれる方法もありましたが、Node.jsを選択してダウンロードに進めばスムーズに解決できました。

これでRspecもできるようになりました。

終わりに

vagrantからdockerへの移行の手順とエラーの紹介をさせていただきました。
まだまだ勉強不足なところも多いため、dockerを十分に活用できていない点が多くあると改めて思いました。
参考にさせていただいた記事が多かったこともあり、当初思っていたよりも短い時間で導入まではできたので挑戦してみて良かったと感じています。

以前のvagrant環境と同じことはできるようになったので、次はCircleCIの導入でより良い開発環境にできるよう学習を続けたいと思います。

初投稿ということもあり、わかりにくい部分もあったかとは思いますが最後までみていただきありがとうございました!

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

Vagrant上で作成したポートフォリオをDockerの開発環境に移行してみた

はじめに

初めまして、初投稿になりますので自己紹介と記事のきっかけについて書かせていただきます。

  • 自己紹介
    2020年9月にDMMWEBCAMPを卒業し、現在転職活動中のエンジニアです。

  • 記事のきっかけ
    他のプログラミングスクールが同じかはわかりませんが、スクール生で共通の開発環境にするために全てvagrant上で仮想環境を構築しカリキュラムとポートフォリオの作成を行っていました。そのため、0からの自力で環境構築を行ったことがなく実務を意識するのならDockerぐらいは自力で入れておきたいと思い学習し始めたのがきっかけです。
    まだまだ勉強中のため、正直かなり怪しいところは多いとは思いますが同じような勉強中の方の参考に少しでもなれば嬉しいです!

この記事で扱うこと

  • Dockerを使用した開発環境の構築
    Vagrant上でできていたことがDocker上でもできるようにするイメージです

  • Dockerfile、docker-composeなどの表記
    参考までに、Vagrantを使用していた時の開発環境は下記になります

    • OS:macOS Catalina 10.15.4
    • バックエンド: Ruby 2.5.7、Rails 5.2.4.3
    • 開発環境: Vagrant 2.2.4、SQLite (※MySQLやPostgreSQLの場合、この後の見本の表記が異なります)
    • 本番環境: MySQL2、AWS (EC2、RDS for MySQL、Route53)、Nginx、 Puma、Capistrano

スクリーンショット 2020-09-13 12.40.05.png

  • 0からの開発環境の構築で起こりうるエラーと解決した方法
    記事のきっかけにもあるように、ローカル環境の構築自体も初めてのためdocker以前のエラーもいくつかありました。解決のために参考にさせていただいた記事をメインに紹介していきたいと思います。

この記事で扱わないこと

  • Dockerfile、docker-composeなどの基礎知識

    Dockerに関する記事やyoutube多くありますがほとんどがコピペでなんとなく進んでしまうので、最初は自分でドキュメントを読みながら手を動かして全体の仕組みをなんとなく先に掴んでおくのが理解しやすいと思いました。
    参考までに自分は下記サイトで学習を始めました。各用語調べながらでも半日〜2日ほどでできる内容かつDockerのイメージが掴めるかと思いますのでおすすめです。

事前準備

$ docker version
Client: Docker Engine - Community
 Cloud integration  0.1.18
 Version:           19.03.13
 API version:       1.40
  • ポートフォリオ(Dockerの開発環境にしたいプロジェクト)をgit cloneしてくる
    記事のきっかけにもありますよう、既にポートフォリオを作成している段階のため環境構築したいものをgithubからローカル上に持ってきましょう
$ mkdir docker
 # Desktopからもしくはvagrantを使用していた時のworkディレクトリなどで実行
$ git clone git@github.com:ユーザー名/リポジトリ名.git

各ファイルの準備

Dockerとプロジェクトの準備ができたので、環境構築に必要なファイルを作成していきます

①Dockerfile

# 先ほどクローンしたディレクトリ上で
$ touch Dockerfile

下記内容を記入していきましょう

# 元となるdocker imageを指定。ポートフォリオのrubyのバージョンと合わせましょう
FROM ruby:2.5.7

RUN apt-get update -qq && apt-get install -y nodejs postgresql-client

RUN apt-get update && apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/*
#sqlite3を指定
RUN apt-get update && apt-get install -y sqlite3 --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

# コンテナ起動した時に、コンテナ内でmyappディレクトリを作成
RUN mkdir /myapp

# コンテナ内の作業ディレクトリの指定(上のRUNで作成したディレクトリ)
WORKDIR /myapp

# ホストマシンのgemfileをコピーし、コンテナ内のディレクトリに貼り付け
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock

# 貼り付けたgemfileを読み込み。bundlerバージョンが2以降だとこれが必要
ENV BUNDLER_VERSION 2.1.4
RUN gem install bundler
RUN bundle install

# アプリケーション(カレントディレクトリ)をコピーして、コンテナ内のディレクトリに貼り付け
COPY . /myapp

# ポートを3000で公開
EXPOSE 3000

# コンテナにてコマンドを実行
CMD ["rails", "server", "-b", "0.0.0.0", "-p", "3000"]
  • ENV BUNDLER_VERSION 2.1.4について
    Gefile.lockの一番下にこのような表記があるかと思いますので確認してみて下さい
Gemfile.lock
BUNDLED WITH
   2.1.4

②docker-compose.yml

# 同じディレクトリ上で
$ touch docker-compose.yml

下記内容を記入していきましょう

docker-compose.yml
version: '3'
services:
  web:
    build:
      context: ./
      dockerfile: Dockerfile 
    # 変更を保存するファイルを指定
    volumes:
      - .:/myapp
    # ポートを指定
    ports:
      - "3000:3000"
    # コンテナの起動を維持させる
    tty: true
    stdin_open: true

MySQLやPostgreSQLを使用している場合だと、web+DBの記述も必要なのですがSQLiteの場合はこちらのみで大丈夫みたいです。まだこの辺りの知識が乏しいため、ゆくゆくはMySQLに変更したいと思っております・・・

コンテナを起動してみましょう

# Dockerfileを元にdocker imageの作成
$ docker-compose build 

# docker container をバックグランドで起動
$ dokcer-compose up -d

# コンテナ内にてmigrationを更新
docker-compose run web rails db:migrate

これで、いつものlocalhost:3000で表示ができればひと段落です!

引っかかったエラーと解決した方法

大体ですが引っかかった時系列順に記載していきます。最初にも記載しておりますが、エラー内容とおそらくの原因の考察がサブで、メインが解決参考にさせていただいた記事の紹介になります。ありがとうございました

①ActiveRecord::PendingMigrationError Migrations are pending

「dokcer-compose up -d で動いているのにっ・・・!」

$ docker-compose up -d                                                  
Creating network "~~~_default" with the default driver
Creating ~~~_web_1 ... done

localhost:3000のページへアクセス時に起きたエラーです
コンテナの起動後、docker-compose run web rails db:migrate を行いましょう、あるあるな気がします
参考:docker-compose upで立ち上げたRailsでActiveRecord::PendingMigrationError Migrations are pending

②To install the missing version, run 'gem install bundler:2.1.4'

「bundleで始まるコマンド(デプロイ・Rspecなど)ができないっ・・・!」

Traceback (most recent call last):
    2: from 
    1: from 
~
Could not find 'bundler' (2.1.4) required by your /Users/ユーザー名/ディレクトリ名/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.1.4`

Dockerfileを作成した時に確認した、Gemfile.lockの「BUNDLED WITH 2.1.4」がローカル上で見つからないイメージです。
参考:To install the missing version, run gem install bundler:2.1.4と出たときの対処法

エラー文の通りにインストールを試してみましょう、ダメだった場合は③へ

gem install bundler -v 2.1.4

③ERROR: While executing gem ... (Gem::FilePermissionError)

ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /Library/Ruby/Gems/2.3.0 directory.

「gemのインストールの権限がありませんっ・・・!」
sudoを頭につけて行うという記事も多かったですが、そのままインストールよりも「rbenv」というバージョン管理のツールを用いて行う方法の方が今後のことを考えるといい気がします。

参考1:gem installでpermissionエラーになった時の対応方法
参考2:rbenvによるRubyのインストールからHello, World!まで

rbenvの用意ができたら、もう一度やってみましょう

gem install bundler -v 2.1.4

④bundle installができない

「bundleが・・・まだダメっ・・・!」

gem installができたところで、デプロイなどの「bundle~」系を入力するとまだエラー文がでます

Install missing gem executables with `bundle install`

エラー文に従って、bundle installをしてみると

# 長いため、最後の部分だけ抜粋
An error occurred while installing mysql2 (0.5.3), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  mysql2

mysql周辺のエラーですが、原因は自力ではわかりませんでした。
下記の参考にさせていただいた記事通りで私は解決できました、本当にありがたい。

参考:【Rails】MySQL2がbundle installできない時の対応方法

これでデプロイはできるようになると思います。

⑤Could not find a JavaScript runtime ~

「動いているけどっ・・・!」

Rspecを走らせた時のエラーです

$ bundle exec rspec spec/ --format documentation

An error occurred while loading ./spec/models/cart_item_spec.rb.
Failure/Error: require File.expand_path('../config/environment', __dir__)

ExecJS::RuntimeUnavailable:
  Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes.

githubに飛んでみると、ExecJSのどれかをダウンロードするよう勧めています。
gemをいれる方法もありましたが、Node.jsを選択してダウンロードに進めばスムーズに解決できました。

これでRspecもできるようになりました。

終わりに

vagrantからdockerへの移行の手順とエラーの紹介をさせていただきました。
まだまだ勉強不足なところも多いため、dockerを十分に活用できていない点が多くあると改めて思いました。
参考にさせていただいた記事が多かったこともあり、当初思っていたよりも短い時間で導入まではできたので挑戦してみて良かったと感じています。

以前のvagrant環境と同じことはできるようになったので、次はCircleCIの導入でより良い開発環境にできるよう学習を続けたいと思います。

初投稿ということもあり、わかりにくい部分もあったかとは思いますが最後までみていただきありがとうございました!

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

外部キーを持つデータの削除方法

環境

この記事ではmacOS Catalina10.15.6にインストールしたRuby 2.6.5を使っています。

前提

3つのモデルを作成し、以下の状況となっています。

User / ユーザー
Report / レポート
Comment / ユーザーとレポートの関連付けテーブル

コメントがついているレポートの削除を行う際に、以下のエラーが発生しました。

エラー発生

ActiveRecord::InvalidForeignKey in ReportsController#destroy
外部キーエラー
スクリーンショット 2020-10-22 10.00.46.png

何が原因なのか、その解決策を考えます。

原因

レポートの削除を行う際に、コメントテーブルのユーザーとレポートのidの値がなくなることが原因と判断。(外部キー制約のため)
ユーザーとレポートのidの関連付けがなくなったものは、コメントも削除できるようにしたい。

解決策

dependent: :destroyUserモデルReportモデルに記述する。
この記述により、親モデル(Report)を削除する際に、その親モデルに紐づく「子モデル(Comment)」も一緒に削除できる」ようになります。

検証

app/models/user.rb
class User < ApplicationRecord
  has_many :reports
  has_many :comments, dependent: :destroy
end
app/models/report.rb
class Report < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy
end

無事解決しました!
以上です。
同じような悩みや壁にぶつかっている方のお役に立てれば幸いです!

参考

下記を参考にしたので、より深く内容を理解したい方は、見てみてください!
https://qiita.com/Ushinji/items/650fa295a3054d2fe582
https://qiita.com/ITmanbow/items/2170ccaceafd5d401df8
https://qiita.com/Tsh-43879562/items/fbc968453a7063776637

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

備忘録

テンプレートエンジン

テンプレートと呼ばれる通常のhtmlで書いたコードと変数などプログラミングによって書かれたコードを合成してビューファイルを作り出すものです。
ex)
erb haml slim

postとgetの違い

Getは、指定したURLの内容をWebサーバから取り出す際に使用し、
Postは、クライアントが入力した内容をWebサーバに送る際に使用する。
という点が違う。
そのようにする理由は、
入力されたデータをgetで送信してしまうと、入力されたデータがurlの後に付与して送信される為、個人情報やパスワードが丸見えになってしまう為postを使用して送信する。

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

【Rails】System Spec 統合テストの同じ記述をまとめる

ディレクトリとファイルを用意

例として、サインインについての記述をまとめる
specディレクトリ配下にsupportディレクトリを作成
Screen Shot 2020-10-22 at 10.47.58.png

モジュールファイルへの記述

sign_in_support.rb
module SignInSupport
  def sign_in(user)
    visit new_user_session_path
    fill_in 'Email', with: user.email
    fill_in 'Password', with: user.password
    find('input[name="commit"]').click
    expect(current_path).to eq root_path
  end
end

作成したファイルを読み込めるように、rails_helper.rbを編集

rails_helper.rbのコメントアウトを外してモジュールを設定する。

SignInSupport = ジュールファイルのsign_in_support

rails_helper.rb
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
rails_helper.rb
RSpec.configure do |config|
  config.include SignInSupport #SignInSupport = ジュールファイル名のsign_in_support
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

#以下省略

テストファイルへの記述

spec/system/tweets_spec.rb
require 'rails_helper'

RSpec.describe 'ツイート投稿', type: :system do
  before do
    @user = FactoryBot.create(:user)
    @tweet_text = Faker::Lorem.sentence
    @tweet_image = Faker::Lorem.sentence
  end
  context 'ツイート投稿ができるとき'do
    it 'ログインしたユーザーは新規投稿できる' do
      # ログインする
      sign_in(@user)
      # 新規投稿ページへのリンクがあることを確認する
      expect(page).to have_content('投稿する')
      # 投稿ページに移動する
      visit new_tweet_path
      # フォームに情報を入力する
      fill_in 'tweet_image', with: @tweet_image
      fill_in 'tweet_text', with: @tweet_text
      # 送信するとTweetモデルのカウントが1上がることを確認する
      expect{
        find('input[name="commit"]').click
      }.to change { Tweet.count }.by(1)
      # 投稿完了ページに遷移することを確認する
      expect(current_path).to eq tweets_path
      # 「投稿が完了しました」の文字があることを確認する
      expect(page).to have_content('投稿が完了しました。')
      # トップページに遷移する
      visit root_path
      # トップページには先ほど投稿した内容のツイートが存在することを確認する(画像)
      expect(page).to have_selector ".content_post[style='background-image: url(#{@tweet_image});']"
      # トップページには先ほど投稿した内容のツイートが存在することを確認する(テキスト)
      expect(page).to have_content(@tweet_text)
    end
  end
  context 'ツイート投稿ができないとき'do
    it 'ログインしていないと新規投稿ページに遷移できない' do
      # トップページに遷移する
      visit root_path
      # 新規投稿ページへのリンクがないことを確認する
      expect(page).to have_no_content('投稿する')
    end
  end
end

RSpec.describe 'ツイート編集', type: :system do
  before do
    @tweet1 = FactoryBot.create(:tweet)
    @tweet2 = FactoryBot.create(:tweet)
  end
  context 'ツイート編集ができるとき' do
    it 'ログインしたユーザーは自分が投稿したツイートの編集ができる' do
      # ツイート1を投稿したユーザーでログインする
      sign_in(@tweet1.user)
      # ツイート1に「編集」ボタンがあることを確認する
      expect(
        all(".more")[1].hover
      ).to have_link '編集', href: edit_tweet_path(@tweet1)
      # 編集ページへ遷移する
      visit edit_tweet_path(@tweet1)
      # すでに投稿済みの内容がフォームに入っていることを確認する
      expect(
        find('#tweet_image').value
      ).to eq @tweet1.image
      expect(
        find('#tweet_text').value
      ).to eq @tweet1.text
      # 投稿内容を編集する
      fill_in 'tweet_image', with: "#{@tweet1.image}+編集した画像URL"
      fill_in 'tweet_text', with: "#{@tweet1.text}+編集したテキスト"
      # 編集してもTweetモデルのカウントは変わらないことを確認する
      expect{
        find('input[name="commit"]').click
      }.to change { Tweet.count }.by(0)
      # 編集完了画面に遷移することを確認する
      expect(current_path).to eq tweet_path(@tweet1)
      # 「更新が完了しました」の文字があることを確認する
      expect(page).to have_content('更新が完了しました。')
      # トップページに遷移する
      visit root_path
      # トップページには先ほど変更した内容のツイートが存在することを確認する(画像)
      expect(page).to have_selector ".content_post[style='background-image: url(#{@tweet1.image}+編集した画像URL);']"
      # トップページには先ほど変更した内容のツイートが存在することを確認する(テキスト)
      expect(page).to have_content("#{@tweet1.text}+編集したテキスト")
    end
  end
  context 'ツイート編集ができないとき' do
    it 'ログインしたユーザーは自分以外が投稿したツイートの編集画面には遷移できない' do
      # ツイート1を投稿したユーザーでログインする
      sign_in(@tweet1.user)
      # ツイート2に「編集」ボタンがないことを確認する
      expect(
        all(".more")[0].hover
      ).to have_no_link '編集', href: edit_tweet_path(@tweet2)
    end
    it 'ログインしていないとツイートの編集画面には遷移できない' do
      # トップページにいる
      visit root_path
      # ツイート1に「編集」ボタンがないことを確認する
      expect(
        all(".more")[1].hover
      ).to have_no_link '編集', href: edit_tweet_path(@tweet1)
      # ツイート2に「編集」ボタンがないことを確認する
      expect(
        all(".more")[0].hover
      ).to have_no_link '編集', href: edit_tweet_path(@tweet2)
    end
  end
end

RSpec.describe 'ツイート削除', type: :system do
  before do
    @tweet1 = FactoryBot.create(:tweet)
    @tweet2 = FactoryBot.create(:tweet)
  end
  context 'ツイート削除ができるとき' do
    it 'ログインしたユーザーは自らが投稿したツイートの削除ができる' do
      # ツイート1を投稿したユーザーでログインする
      sign_in(@tweet1.user)
      # ツイート1に「削除」ボタンがあることを確認する
      expect(
        all(".more")[1].hover
      ).to have_link '削除', href: tweet_path(@tweet1)
      # 投稿を削除するとレコードの数が1減ることを確認する
      expect{
        all(".more")[1].hover.find_link('削除', href: tweet_path(@tweet1)).click
      }.to change { Tweet.count }.by(-1)
      # 削除完了画面に遷移することを確認する
      expect(current_path).to eq tweet_path(@tweet1)
      # 「削除が完了しました」の文字があることを確認する
      expect(page).to have_content('削除が完了しました。')
      # トップページに遷移する
      visit root_path
      # トップページにはツイート1の内容が存在しないことを確認する(画像)
      expect(page).to have_no_selector ".content_post[style='background-image: url(#{@tweet1.image});']"
      # トップページにはツイート1の内容が存在しないことを確認する(テキスト)
      expect(page).to have_no_content("#{@tweet1.text}")
    end
  end
  context 'ツイート削除ができないとき' do
    it 'ログインしたユーザーは自分以外が投稿したツイートの削除ができない' do
      # ツイート1を投稿したユーザーでログインする
      sign_in(@tweet1.user)
      # ツイート2に「削除」ボタンが無いことを確認する
      expect(
        all(".more")[0].hover
      ).to have_no_link '削除', href: tweet_path(@tweet2)
    end
    it 'ログインしていないとツイートの削除ボタンがない' do
      # トップページに移動する
      visit root_path
      # ツイート1に「削除」ボタンが無いことを確認する
      expect(
        all(".more")[1].hover
      ).to have_no_link '削除', href: tweet_path(@tweet1)
      # ツイート2に「削除」ボタンが無いことを確認する
      expect(
        all(".more")[0].hover
      ).to have_no_link '削除', href: tweet_path(@tweet2)
    end
  end
end

RSpec.describe 'ツイート詳細', type: :system do
  before do
    @tweet = FactoryBot.create(:tweet)
  end
  it 'ログインしたユーザーはツイート詳細ページに遷移してコメント投稿欄が表示される' do
    # ログインする
    sign_in(@tweet.user)
    # ツイートに「詳細」ボタンがあることを確認する
    expect(
      all(".more")[0].hover
    ).to have_link '詳細', href: tweet_path(@tweet)
    # 詳細ページに遷移する
    visit tweet_path(@tweet)
    # 詳細ページにツイートの内容が含まれていることを確認する
    expect(page).to have_selector ".content_post[style='background-image: url(#{@tweet.image});']"
    expect(page).to have_content("#{@tweet.text}")
    # コメント用のフォームが存在することを確認する
    expect(page).to have_selector 'form'
  end
  it 'ログインしていない状態でツイート詳細ページに遷移できるもののコメント投稿欄が表示されない' do
    # トップページに移動する
    visit root_path
    # ツイートに「詳細」ボタンがあることを確認する
    expect(
      all(".more")[0].hover
    ).to have_link '詳細', href: tweet_path(@tweet)
    # 詳細ページに遷移する
    visit tweet_path(@tweet)
    # 詳細ページにツイートの内容が含まれていることを確認する
    expect(page).to have_selector ".content_post[style='background-image: url(#{@tweet.image});']"
    expect(page).to have_content("#{@tweet.text}")
    # フォームが存在しないことを確認することを確認する
    expect(page).to have_no_selector 'form'
    # 「コメントの投稿には新規登録/ログインが必要です」が表示されていることを確認する
    expect(page).to have_content 'コメントの投稿には新規登録/ログインが必要です'
  end
end
spec/system/comments_spec.rb
require 'rails_helper'

RSpec.describe 'コメント投稿', type: :system do
  before do
    @tweet = FactoryBot.create(:tweet)
    @comment = Faker::Lorem.sentence
  end

  it 'ログインしたユーザーはツイート詳細ページでコメント投稿できる' do
    # ログインする
    sign_in(@tweet.user)
    # ツイート詳細ページに遷移する
    visit tweet_path(@tweet)
    # フォームに情報を入力する
    fill_in 'comment_text', with: @comment
    # コメントを送信すると、Commentモデルのカウントが1上がることを確認する
    expect{
      find('input[name="commit"]').click
    }.to change { Comment.count }.by(1)
    # 詳細ページにリダイレクトされることを確認する
    expect(current_path).to eq tweet_path(@tweet)
    # 詳細ページ上に先ほどのコメント内容が含まれていることを確認する
    expect(page).to have_content @comment
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsコントローラーで同じ記述をprivate以下にメソッドとして切り分け、before_actionで呼び出せるように実装してみた

この記事ではインストールしたRails 6.0.0を使っています。
*初めての投稿です。見づらければ申し訳ないです:smile_cat:

やること

複数の記述をまとめる
コントローラーを見やすくする
privateメゾットを使用
before_actionを使用

初期コード

items_controller.rb
def show 
   @item = Item.find(params[:id])
end

def edit
   @item = Item.find(params[:id])
end

def update
   @item = Item.find(params[:id])
end 
【見ていていただくとわかるように同じような記述が複数のアクション内で使われています。こちらを切り分けて呼び出したいと思います!】

方法1 〜private以下に切り分けてあげる〜

items_controller.rb
private
 def set_item
    @item = Item.find(params[:id])
  end

まず初期コードにある@(インスタンス変数)の記述を削除します。
そしてprivateを下部に作ります。メゾット set_itemを作りその中に先ほど削除した記述を入れてあげます。
(メゾット名はわかり易ければなんでも大丈夫です)
こうすることで、メゾットとして切り分けができます。ですがこれだけでは、切り出しただけで呼び出すことができません。

方法2 ~before_actionで呼び出してあげる〜

items_controller.rb
before_action :set_item, only: [:show, :edit, :update]

コントローラー上部にこちらを記述します。一つ一つ説明すると
before_action :各アクションが実行される前に呼び出すための記述
set_item :  方法1のprivate内で作ったメゾット名
only:    特定のアクションだけでつかいますよという感じの意味。
反対語としてexpectがあり、それは特定のアクション以外で使うという意味!
そのあとは指定するアクション名を記述!

最後に

初期コードの方が分かりやすいしやる意味あるかな?と最初は思うかもしれません。僕もそうでした。笑
ですが例えばこのあと他のアクションにまた同じ記述が必要になった時などに、before_actionにアクション名を追加するだけになるのでとても便利です!
コードを他の方がみても分かりやすく長ったるい記述じゃないように書けます!
これからも他の方が見やすいコードをかけるよう意識していこうと思っています!

    

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

文字列

文字列

文字列は、プログラミングの中で文字を扱うための値です。

文字列を生成するには、文字をダブルクォーテーション"
または、シングルクォーテーション'で囲みます。
以下の例を見てください。

【例】Rubyファイル
"This is string."

'This is string, too.'

このように、文字列は簡単に生成できます。

irbで以下のコードを実行してみましょう

irbでRubyの文字列を記述し、実行してみましょう。

ターミナル
# irbを起動
% irb
irb
# 文字列を書いてエンターキーで実行
irb(main):001:0> "Hello! こんにちは!"

# 続けてこのように表示されれば成功
=> "Hello! こんにちは!"

全角スペースや日本語が含まれているとプログラムはエラーが生じますが、
文字列の値として扱う場合は、エラーにはなりません。

まとめ

文字列とは、プログラミングの中で文字を扱うための値のこと。

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

【ruby】文字列末尾への挿入、置換、破壊【b021】

某スキルチェックの際にググったメソッド忘備録 兼 初投稿

1.出力された値の取得


ループ処理の中に任意の数、入力された文字を配列として取得
qiita.rb
hoge  #入力される文字
huga  #入力される文字
piga  #入力される文字

lines = []  #予め配列の変数を定義
lines << gets.chomp  #改行がないようにchomp

・複数行にわたって文字列がくるので配列に追加していく

qiita.rb
lines = ["hoge", "fuga", "piga"]
puts lines[0]  #インデックス番号(添字)を指定して任意の文字を出力
>> hoge

・インデックス番号で任意の文字を呼び出し条件によって必要な変更を行い出力

2.文字末尾へ挿入

文字列末尾の種類によって挿入するか置換するかの条件分岐を考えます。

qiita.rb
#条件分岐
文字列.end_with?('探したい値')  #任意の文字末尾に探したい値があるか
puts hoge.end_with?('e', 'ge', 'ch')  #複数の値を一度に参照可能(どれか一つでもあればおk)
>> ture

#挿入メソッド
文字列.insert(挿入先のインデックス番号,"挿入する値") 
#['h','o','g','e']のインデックス番号は先頭からだと[0,1,2,3]、末尾からだと[-4,-3,-2,-1]
puts hoge.insert(-1,"s")  #末尾へ挿入
>> hoges

puts hoge.insert(0,"s")  #先頭へ挿入
>> shoge

#破壊的メソッド
文字列.delete_suffix("消す値")  #末尾の消す値を消去
puts gehoge.delete_suffix("ge")
>> geho  #先頭は消えない

#置換(末尾"ge"を"ver"へ変換)
puts gehoge.delete_suffix("ge").insert(-1,"ver")
>> hehover

参考
https://qiita.com/uuchan/items/a4e9382440bdc4d2ac75
https://qiita.com/prgseek/items/92b49fe6b0a579f9cdd8

さくっと置換できれば良かったが難しかったので合わせ技でゴリ押した
もっと良い方法を知っている方、コメントいただければ幸いです!

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

RubyのKernelモジュールについて

まとめようと思ったきっかけ

普段しれっとputsとかprintとかとか使ってるけど、これって何者?と思ったので調べてみると、カーネルモジュールに含まれているメソッドらしい。「Kernelモジュール?」となったので調べてみた。

Kernelモジュール

全てのクラスから参照できるメソッドを定義しているモジュール。 Object クラスはこのモジュールをインクルードしています。
Object クラスのメソッドは実際にはこのモジュールで定義されています。これはトップレベルでのメソッドの再定義に対応するためです。

まず、Objectクラスという全てのスーパークラスがあって、ここで定義されたメソッドは全てのオブジェクトに使える。
そしてKarnelモジュールはこのObjectクラスに含まれているので、結果、どこでもメソッドが使えるということ。
(ちなみにモジュールとは、クラスのようにインスタンス化ができないが、定数やメソッドを定義できる機能。)

こんな感じ。

talk.rb
#この部分がトップレベル(クラスやモジュールの外側)
def say
  puts "hello"
end
#この部分がトップレベル

#Humanクラスは親クラスのObjectを継承しているのでsayメソッドが使える。
class Human
  def talk
    say
  end
end

human = Human.new()
human.talk
#=>hello

最後に

自分なりに調べたのですが、もし間違いがあったらご指摘いただけると幸いです。

参考文献

オブジェクト指向スクリプト言語 Ruby リファレンスマニュアル
https://docs.ruby-lang.org/ja/latest/class/Kernel.html

Rubyにおけるトップレベル
https://www.javadrive.jp/ruby/method/index1.html

Ruby のトップレベルメソッドって結局なんなの
https://qiita.com/pink_bangbi/items/c08ec7b32fc6dd20baad

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