20201009のRailsに関する記事は28件です。

続・ActiveStorageを使って、画像投稿機能実装までの流れ

はじめに

 ActiveStorageを使った画像保存から表示までの説明。前回の投稿で、ActiveStorageの導入方法については説明している。

リンク↓
ActiveStorageを使って、画像投稿機能実装までの流れ

画像の保存から表示までの流れ

  1. アソシエーションの定義
  2. ストロングパラメーターに画像保存の許可をする
  3. HTMLのimg要素として生成
  4. エラーの解消
  5. 画像の大きさ調整
  6. バリデーションの設定

1. アソシエーションの定義

has_one_attachedメソッドを用いて、各レコードと画像ファイルを1対1の関係で紐づける。
紐づけたいモデルファイルに記述

model
class モデル < ApplicationRecord
 has_one_attached :ファイル名
end

ファイル名には、呼び出す際の名前を設定する。また、パラメーターのキーになる。
ex):image, :file etc
イメージとしては、ファイル名のカラムができて、そこに添付ファイルが入っているような感じ。
このアソシエーションをすることで、
モデル名.ファイル名で添付したファイルにアクセスすることができる。

2. ストロングパラメーターに画像保存の許可をする

controllers
 params.require(:message).permit(:text, :image)

アソシエーションの定義のときにも取り上げたように、パラメーターのキーにファイル名を用いる。このときにも、カラムができているイメージというのは当てはまるだろう。

以上が、保存までの流れ。

以下は、表示するまでの流れ。

3. HTMLのimg要素として生成

image_tagメソッドというヘルパーメソッドを用いて、HTMLにimgタグを生成する。

html.erb
<%= image_tag モデル名.ファイル名 %>
<%= image_tag message.image %>  #例

アソシエーションしているため、モデル名.ファイル名だけの記述で画像を表示させられる。

基本的にはここまでで、画像を表示できる。しかしこのままでは、画像が必ずなければ、エラーになってしまう。

4. エラーの解消

 先のimage_tagメソッドにif文で条件分岐させる。

html.erb
<%= image_tag message.image, if message.image.attached? %>

attached?メソッド で画像が添付されていると、trueを返し、読み込まれる。画像が添付されていないと、falseを返し、読み込まれず、エラーが起きない。

5. 画像の大きさ調整

ActiveStorageを導入していると使えるメソッドのvariantメソッドを使う。
先のコードに付け足すと、

html.erb
<%= image_tag message.image.variant(resize: '500×500'), if message.image.attached? %>

画像の表示サイズを指定すると、それ以上に大きくならない。

6. バリデーションの設定

現状だと、LINEをイメージすると、テキストと画像が必ず両方ないとエラーになる。使い勝手がよくないため、テキストと画像のどちらかが存在すればよい、という仕様にする。

model
validates :message, presence: true, unless: :was_attacher?

def was_attached?
 self.image.attached?
end

メソッドの返り値がfalseならば、バリデーションの検証を行う。(messageが存在しないとダメ)

最後に

アソシエーションするだけで、簡単に画像にアクセスできるのが便利すぎる!!

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

Railsチュートリアル Herokuへのデプロイのエラーを解決したので解決方法をメモ

初めてのqiita投稿です。
Rails-Tutorialの第一章 1.5.2 Herokuへのデプロイでいきなり躓きました。ググってもなかなか解決策が出て来なくて序盤で手こずったので、解決策を覚書。

◆症状

Rails-Tutorialのテキストの通り進めていったところ、
"git push Heroku main"を行い、無事デプロイまで進んだように見えたが、以下の画面となった。
(本当なら「Hello, World!」と表示されるはずであった。)
スクリーンショット 2020-10-09 224816.png

◆原因

原因は、Heroku上の別のリポジトリへデプロイを行っていたこと。
詳細は分からないけれど、元をたどれば、デプロイ前に一度食事で手を止めたせいで、デプロイ前のherokuコマンド"heroku create"を2回行ってしまっていたことが原因と思われる。

  デプロイ先    : 1回目に作成したリポジトリ
  閲覧していたURL: 2回目に作成したリポジトリ

上記の通りとなっていた。

◆解決策

①閲覧していた2回目に作成したリポジトリに、push先を変更した。(下記コマンドの実行)
Terminal
git remote set-url heroku ["heroku create"の実行時にターミナルに表示されるgitのリポジトリ ]
②再度pushの実行
Terminal
git push heroku main

※Railsチュートリアルには"main"が"master"と記載されているけれど、githubの仕様変更?で、この記事の作成時点(2020年10月9日現在)ではmainで正となっている。

スクリーンショット 2020-10-09 230523.png

無事画面が表示された。("hello, world!"じゃなかった。)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[ActiveStorage] ファイルのURLにアクセスしたときに routes.rb 末尾の *path にマッチしてしまうのを避ける

config/routes.rb の末尾に *path などのルーティングがあると、
ActiveStorageのファイルを示すエンドポイント(/rails/active_storage/...)1よりも先にマッチしてしまう。

config/routes.rb
Rails.application.routes.draw do
  ...

  get "*path" , to: redirect('/')
end

これを回避するためには以下のようにする。

config/routes.rb
Rails.application.routes.draw do
  ...

  get '*path' , to: redirect('/'), constraints: lambda { |req|
    req.path.exclude? 'rails/active_storage'
  }
end

これで rails/active_storage を含むパスがマッチしないように設定できる。

constraints オプションについて

特定のルーティングに制約(constraints)を与えるためのオプションという解釈。
以下のようにして使う。

# match `/users/A12345`, but not `/users/893`
get 'users/:id', to: 'users#show', constraints: { id: /[A-Z]\d{5}/ }

# match port 3000 only
get :users, to: 'users#index', constraints: { port: '3000' }

上述のようにlambdaを指定することもできる。2

get "hi", to: redirect("/foo"), constraints: ->(req) { true }

参考


  1. 定義はこちら https://github.com/rails/rails/blob/v5.2.4.4/activestorage/config/routes.rb#L4 

  2. 厳密には request を引数に取る call メソッドが呼び出し可能なオブジェクトを指定すればよさそう。リクエスト毎に呼ばれる call がtrueを返す場合にのみ、対象のルーティングがマッチするといった動作のよう。本投稿では触れていないが matches? でも可。 ( テストコード実装部分 を参照) 

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

【Ruby on Rails】RSpec実行時にgem 'chromedriver-helper'は非推奨ですよって警告を受ける。

前書き・編集点

この記事は前回の記事
【Ruby on Rails】RSpec実行時にgemのバージョン違いで警告を受ける。
の続きですが、全く別の警告だったので2つに分けました。

2020.10.10
コメントで、私の解釈の違いについてご指摘をいただきましたのでその点修正を行いました。

環境

Ruby 2.5.7
Rails 5.2.4

gem

gem 'rspec-rails', '~> 3.6'

経緯

RSpecを使ってテストコードを記述し、実行すると下記のような警告文が出ました。

$ rspec spec/models/tag_spec.rb 

2020-10-09 09:13:13 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.

これはエラーではないのでテストは正常に実行されますが、毎回出るのを放っておくわけにも行かないのでこれも解決していきます。

手順

警告文の内容ですが、
Selenium::WebDriver::Chrome#driver_path= is deprecated.
Chromeクラスのdriver_path=メソッドが非推奨であるため、
Selenium::WebDriver::Chrome::Service#driver_path= instead.
Serviceクラスのdriver_path=メソッドを代わりに使うようにというメッセージです。

こちらに書かれている通り、私のGemfileにもgem 'chromedriver-helper'がありましたので、まずはこれをコメントアウトします。
次に代わりとなる
gem 'webdrivers'を記述します。

最後に

$ bundle install

で解決することができました。

まとめ

質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。

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

参考サイト

私のGitHubに実際に使っているファイルを公開しているのでそちらも参考にしていただければと思います!
GitHub - MasaoSasaki/matchi

その他
Qiita - サポートが終了したchromedriver-helperからwebdrivers gemに移行する手順
Qiita - rspec-rails 3.7の新機能!System Specを使ってみた

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

【Rails,JS】コメントの非同期表示を実装する方法

この記事では、コメントを非同期で表示できる実装方法を解説します。

  • Rails_6.0.0_ を使用しています。
  • 商品が出品・購入できるアプリケーションにコメントをつけます。
  • それぞれの商品(item)の詳細ページにコメントをする場所があります。
  • コメントの保存や送信に必要なRubyのコーディングと、保存したコメントを即時に表示させるJavaScriptのコーディングを行います。
  • すでにコメント投稿機能は完成している体で、非同期機能だけ実装していきます。

画面収録 2020-10-06 -1.mov.gif

実装内容

  • channelを用いて実装を行う
  • コメントを非同期で表示
  • コメントには名前(name)、コメントされた日付(created_at)、コメント内容(text)を表示

channelとは?

channelとは?

チャネルとは、即時更新機能を実現するサーバー側の仕組みのことです。データの経路を設定したり、送られてきたデータをクライアントの画面上に表示させたりします。

channelでどんなことができる?

データの経路を設定したり、送られてきたデータを表示させるJavaScriptを記述すれば、送信したデータが非同期で表示できます。

channelのファイル作成

ターミナルで以下コマンドを実行
% rails g channel comment
(commentには自分が作成するファイルの名前を記述)
いくつかファイルができますが、今回は以下2つのファイルを使用します。

app/channel/comment_channel.rb

クライアントとサーバーを結びつけるためのファイルです。

app/javascript/channels/comment_channel.js

サーバーから送られてきたデータをクライアントの画面に表示させるためのファイルです。

comment_channel.rbの記述

class MessageChannel < ApplicationCable::Channel
  def subscribed
    stream_from "comment_channel"
  end

  def unsubscribed
  end
end

stream_from "comment_channel"を記述することでサーバーとクライアントを結びつけることができます。

comments_controller.rbの記述

コントローラーの記述です。
すでに非同期でないコメント実装機能は済んでいる体なので、非同期に関する記述以外の説明は割愛します。

データベースからJSに渡したいデータを記述する

今回JSで反映させたいデータは以下の通りです

  • ユーザーのニックネーム
  • コメントされた時間
  • コメントのテキスト
  • アイテムのid(どの商品にコメントするかを判断するために必要)

この3つの情報を、コントローラーでJSに渡してあげる必要があります。

class CommentsController < ApplicationController
  before_action :authenticate_user!
  def create
    @comment = Comment.new(comment_params)
    @item = Item.find(params[:item_id])
    @comments = @item.comments.includes(:user).order('created_at DESC')
    if @comment.valid?
      @comment.save
            ActionCable.server.broadcast  'comment_channel', content: @comment, nickname: @comment.user.nickname, time: @comment.created_at.strftime("%Y/%m/%d %H:%M:%S"), id: @item.id
    else
      render "items/show"
    end
  end

  private
    def comment_params
    params.require(:comment).permit(:text).merge(user_id: current_user.id, item_id: params[:item_id])
    end
end

今回の実装で書き足したのは以下の1文だけです。
ActionCable.server.broadcast 'comment_channel', content: @comment, nickname: @comment.user.nickname, time: @comment.created_at.strftime("%Y/%m/%d %H:%M:%S"), id: @item.id

contentusertimeidをJSで使用するので、そちらの定義をしてあげました。
content
@commentで定義している:text、Commentテーブルのtextカラム、すなわち入力したコメントのことです。
user
@commentに紐づいているuserのnicknameをとってきています。(commentとuserにアソシエーションを組んでいます。)
time
Commentテーブルのcreated_atカラムです。strftime("%Y/%m/%d %H:%M:%S")と記述することで任意の日付、時刻設定を表示できます。以下の記事を参考にさせていただきました。
strftime を憶えられない (rubyの)
item
自分が今表示しているアイテムのページだけでJSが発火する必要があるので、それを判断するために使用します。

comment_channel.rbの記述(JavaScript)

今回はapp/javascript/channels/comment_channel.jsreceived()部分に記述していきます。

app/javascript/channels/comment_channel.js
received(data) {
}

()の中にdataと記述してあげることで、コントローラーで定義した値がとってこれるようになります。receivedは、受け取るという意味なので、データを受け取ったら、この中に記述しているJSを実行してね!ということになります。
さあ、これからこの中にJSの記述をしていきます!

今開いているアイテムページだけでコメント表示できるように、条件分岐分を書きます。
app/javascript/channels/comment_channel.js
//現在開いているページのURLをゲット
let url = window.location.href
//スラッシュ(/)ごとに要素を取り出す
let param = url.split('/');
//このアプリの場合、URLの一番最後にアイテムidがきており、それをparamItemとして定義
let paramItem = param[param.length-1]
// パラメータid(URLの中に含まれているid)が、コントローラーから送った`data.id`かどうかを判断する
if (paramItem == data.id) {}

if文で、条件分岐をしてあげます。次は、処理の内容を条件分岐分の中に書いてあげます。
記述する内容は、

  • div要素を生成する
  • 生成した要素をブラウザに表示させる
  • 表示させるテキストを生成する

といった流れです。

表示させるためのdivを作る
app/javascript/channels/comment_channel.js
    //表示させる場所のdivのIDをとってくる
    const comments = document.getElementById('comments');
    // すでにあるビューファイルと同じになるようにdivを作成
    const textElement = document.createElement('div');
    textElement.setAttribute('class', "comment-display");

    const topElement = document.createElement('div');
    topElement.setAttribute('class', "comment-top");

    const nameElement = document.createElement('div');
    const timeElement = document.createElement('div');

    const bottomElement = document.createElement('div');
    bottomElement.setAttribute('class', "comment-bottom");

createElementメソッドを使用し、div要素を作成、必要なものにはそれぞれsetAttributeメソッドでclass名を与えてあげます。

ちなみに、コメントを表示するビューは以下の通り

      <div id='comments'>
      </div>
      <% @comments.each do |comment| %>
        <div class='comment-display'>
          <div class='comment-top'>
            <div><%= comment.user.nickname %></div>
            <div><%= l comment.created_at %></div>
          </div>
          <div class='comment-bottom'>
            <p><%= comment.text %></p>
          </div>
        </div>
      <% end %>

div要素を生成しましたが、まだブラウザに表示されていません。ブラウザに表示させ、かつ親子関係を作りましょう。

app/javascript/channels/comment_channel.js
      // 生成したHTMLの要素をブラウザに表示させる
      comments.insertBefore(textElement, comments.firstElementChild);
      textElement.appendChild(topElement);
      textElement.appendChild(bottomElement);
      topElement.appendChild(nameElement);
      topElement.appendChild(timeElement);

insertBefireメソッドと、appendChildメソッドを使用します。
親要素.insertBefore(追加する要素, どこに追加するのか)
親要素.appendChild(追加する要素)
で、insertBefireは任意の場所に、appendChildは親クラスの中の最後のに要素を入れることができます。

もう少し詳しく見たい方は以下をご覧ください
【JavaScript】appendChildとinsertBeforeの違い

div要素が作成できたら、次は表示させる情報をとってきましょう。

app/javascript/channels/comment_channel.js
      const name = `${data.nickname}`;
      nameElement.innerHTML = name;
      const time = `${data.time}`;
      timeElement.innerHTML = time;
      const text = `<p>${data.content.text}</p>`;
      bottomElement.innerHTML = text;

表示させる情報をそれぞれ変数に入れています。dataは、received(data) {}のdataです。コントローラーで定義した値のことです。それぞれcontent,nickname,time,を定義しましたね。
innerHTMLで、既存の要素にHTMLを上書きをします。

ここまでで表示は完了しました!
ですがこのままだと、2つ問題があります。
1. データは表示されたが、コメント入力欄にコメントが残ったままであること
2. HTMLはデフォルトでボタンが1回しか押せない仕様になっていること
これを解決しましょう!

データ送信した後にコメント入力欄のコメントを消す
app/javascript/channels/comment_channel.js
    const newComment = document.getElementById('comment_text');
    newComment.value='';

コメント入力欄のIDをとってきて、そこの値を空にする、という記述です。

何度もコメントボタンを押せるようにする
app/javascript/channels/comment_channel.js
    const inputElement = document.querySelector('input[name="commit"]');
    inputElement.disabled = false;

"コメントする"ボタンの、name属性をとってきて、そこをdisabled = falseとしてあげることで何度もクリック可能になります。

記述をまとめると以下の通りです。
app/javascript/channels/comment_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("CommentChannel", {
  connected() {
  },

  disconnected() {
  },
  // ↓データを受け取ったら実行してね
  received(data) {
    let url = window.location.href
    let param = url.split('/');
    let paramItem = param[param.length-1]
    if (paramItem == data.id) {
      const comments = document.getElementById('comments');
      const comment = document.getElementsByClassName('comment-display');
      //使用する要素の作成
      const textElement = document.createElement('div');
      textElement.setAttribute('class', "comment-display");
      const topElement = document.createElement('div');
      topElement.setAttribute('class', "comment-top");
      const nameElement = document.createElement('div');
      const timeElement = document.createElement('div');
      const bottomElement = document.createElement('div');
      bottomElement.setAttribute('class', "comment-bottom");
      // 生成したHTMLの要素をブラウザに表示させる
      comments.insertBefore(textElement, comments.firstElementChild);
      textElement.appendChild(topElement);
      textElement.appendChild(bottomElement);
      topElement.appendChild(nameElement);
      topElement.appendChild(timeElement);
      // 表示するテキストを生成
      const name = `${data.nickname}`;
      nameElement.innerHTML = name;
      const time = `${data.time}`;
      timeElement.innerHTML = time;
      const text = `<p>${data.content.text}</p>`;
      bottomElement.innerHTML = text;
      //コメントを送った後、コメント欄をからにする
      const newComment = document.getElementById('comment_text');
      newComment.value='';
      //何度もボタンを押せるようにする
      const inputElement = document.querySelector('input[name="commit"]');
      inputElement.disabled = false;
    }
  }
});

おわりに

完成したと思っていましたが、これを書いている時点でいくつもミスや、ちょっとよくわからない記述を発見しました。
リファクタリングがいかに大切かよく分かりました。
正しく無い記述があるかもしれませんが、誰かのお役に立てれば幸いです。

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

【Rails】複数モデルのインスタンスを一覧表示する

したいこと

YouTubeやFacebookのタイムラインのように、投稿と広告を作成降順に同じ画面に表示する機能が必要になった(通常なら広告は作成順の表示ではないかもしれないが、話を簡単にするために作成順で並べると仮定する)。

投稿モデル(Post)と広告モデル(Advertisement)をコントローラーでうまいこと取得して、ビューで並列して処理する方法を考える。

前提

バージョン

  • Ruby 2.6.3
  • Rails 5.2.3

それぞれのモデルのカラム

# 投稿モデルと広告モデルはカラムが異なるとする

# 投稿モデル(Post)
  t.integer "user_id"
  t.string "content"
  t.string "image"
  t.datetime "created_at"
  t.datetime "updated_at"

# 広告モデル(Advertisement)
  t.integer "company_id"
  t.string "content"
  t.string "link_url"
  t.datetime "expired_date"
  t.datetime "created_at"
  t.datetime "updated_at"

実装方法

timeline_controller.rb
def index
  posts = Post.all
  ads = Advertisement.all

  # それぞれの複数インスタンスを1つの配列にする
  @instances = posts | ads
  # 作成降順に並び替え
  @instances.sort!{ |a, b| b.created_at <=> a.created_at }
end
index.html.erb
<% @instances.each do |instance| %>
  <% if instance.class == "Post" %>
    <%# Postの表示 %> 
    <%= instance.user_id %>
    <%= instance.content %>
    <%= instance.image %>
  <% else %>
    <%# Advertisementの表示 %> 
    <%= instance.company_id %>
    <%= instance.content %>
    <%= instance.link_url %>
  <% end %>
<% end %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails】RSpec実行時にgemのバージョン違いで警告を受ける。

前書き・編集点

2020.10.10
コメントより

環境

Ruby 2.5.7
Rails 5.2.4

gem

gem 'rspec-rails', '~> 3.6'

経緯

RSpecでテストコードを書いて、いざ実行すると、次のようなエラーが発生しました。

$ rspec spec/models/tag_spec.rb

WARN: Unresolved specs during Gem::Specification.reset:
      diff-lcs (< 2.0, >= 1.2.0)
WARN: Clearing out unresolved specs.
Please report a bug if this causes problems.

正確にはエラーではなく、同じgemで複数のバージョンがあるからそれの警告を受けているようで、テスト自体は無事に実行されていました。
しかし、放っておくわけにも行かないので、解決していきます。

手順

解決策はこちらの記事を参考にさせていただきました。
Qiita - RubygemでWARN: Clearing out unresolved specs.が出た時の対応

まずは問題のgemについて確認していきます。

$ diff-lcs (< 2.0, >= 1.2.0)

diff-lcsというgemのバージョンが複数あるという指摘でしたが、このgemは自分でインストールした覚えはないので、当然ながらGemfileには記載がありません。
となると、何らかのgemをインストールした時にbundlerが依存gemとして自動的にインストールした可能性があるので、Gemfile.lockを確認すると、rspecのgemの依存以外のところにもdiff-lcs (1.4.4)がありました。

rspecでインストールされている依存gemは2箇所あって、どちらもdiff-lcs (>= 1.2.0, < 2.0)という感じで、バージョンは書かれていないので、このgemのバージョンを次のコマンドで調べていきます。

$ gem list -a | grep diff-lcs

diff-lcs (1.4.4, 1.4.2, 1.3)

調べた結果、どうやら(1.4.4, 1.4.2, 1.3)3つのバージョンがインストールされているようです。
依存していない方のdiff-lsc (1.4.4)がこの中で一番最新ということで、rspec依存の他二つをこのバージョンに合わせていきます。

ここで一つ注意ですが、bundlerで依存しているgemをインストールした場合、依存gemのバージョンを安易に変更してしまうと、依存関係にある他のgemに不具合が起きる可能性があります。
今回の場合だと、rspec依存のdiff-lcs (< 2.0, >= 1.2.0)はバージョンが「1.2.0以上、2.0未満」と書かれてあったので(今回の中で)最新の1.4.4ならバージョンアップしても問題ないという結論に至りました。

次に参考サイトにも書かれてあるコマンドを実行していきます。

$ gem cleanup

そして

$ bundle install

これで警告文が消えました。

手順(依存関係でgemが複数バージョン必要な場合)

こちらコメントより抜粋させていただきます。

$ bundle exec rspec (実行ファイル指定)

を行うと、bundlerがGemfileに記述されている依存関係を自動的に認識してくれます。

依存関係が複雑になって、どうしても複数バージョンが必要な時は、前述した最新バージョンにまとめることはできないので、この方法しかなくなります。

まとめ

私の場合は、これ以外にもう一つ警告文が出ていましたので、それについては次の記事をご覧いただければと思いますヽ(;▽;)ノ
Qiita - 【Ruby on Rails】RSpec実行時にgem 'chromedriver-helper'は非推奨ですよって警告を受ける。

質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。

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

参考サイト

私のGitHubに実際に使っているファイルを公開しているのでそちらも参考にしていただければと思います!
GitHub - MasaoSasaki/matchi

その他
Qiita - RubygemでWARN: Clearing out unresolved specs.が出た時の対応
YoheiIsokawa - 【Rails】Gemfileのバージョン指定の書き方

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

jQueryの導入〜使用できるまで(非同期通信の準備として実行した記録)haml

導入準備

パッケージ管理システムを使用してjQueryの導入をしていく。
今回使用するパッケージ管理システム(パッケージマネージャー)は「yarn」
インストールする。

ターミナル
% yarn install

jQueryを導入

JavaScriptのライブラリの一つであるjQueryを導入
 

Gemfileの中に記述する。

Gemfile
gem 'jquery-rails'
# 最後尾に記入する

gemを記述したら忘れずバンドルインストールをする。

ターミナル
% bundle install

gemを反映させたらサーバを再起動する。
 

JavaScriptを読み込む準備

JavaScriptを記述するディレクトリを作成

assetsフォルダの中に「javascripts」という名前のファイルを作成する


:file_folder: app
  :file_folder: assets
    :file_folder: config
    :file_folder: images
    :file_folder: javascripts
    :file_folder: stylesheets


 

JavaScriptを記述するファイルを作成

javascriptsフォルダの中にapplication.jsを作成する


:file_folder: app
  :file_folder: assets
    :file_folder: config
    :file_folder: images
    :file_folder: javascripts
      :page_facing_up:application.js
    :file_folder: stylesheets


 

JavaScriptを読み込む

JavaScriptのファイルをビューファイルから呼び出してあげる必要がある。
cssと同じ容量。
「= javascript_include_tag 'application'」を9行目に追加。

app/views/layouts/application.html.haml
!!!
%html
  %head
    %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
    %title Sample app
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application'
    -# = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
    -# 上の行はWebpackerという技術に関する記述。不要なら削除。
  %body
    = yield

 
application.jsのファイルを呼び出せるようにする。
「//= link_directory ../javascripts .js」を2行目に記述する。

app/assets/config/manifest.js
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css

これでJavaScriptが読み込まれているはず!
 

JavaScriptが正常に読み込まれているか確認する

application.jsに以下の記述をする。
コンソールログで確認。

app/assets/javascripts/application.js
console.log("JavaScriptは正常に読み込まれています");

ページをリロードして、
コンソールに「JavaScriptは正常に読み込まれています」と出ればオッケー。
スクリーンショット 2020-10-09 17.27.24.png

正常なのが確認できたら、先ほど記述した
application.jsの中の「console.log("JavaScriptは正常に読み込まれています");」を削除しておく。
 

jQueryを使えるようにする

以下の2行をapplication.jsに追記。
jquery-railsという先ほど記載したgemを使うための記述。

app/assets/javascripts/application.js
//= require jquery
//= require rails-ujs

jQueryの読み込み・・・//= require jquery
RailsでJavaScriptを使用する・・・//= require rails-ujs

備考

この後application.js直下にJavaScriptのファイルを作成して記述してく際は
application.jsが存在するディレクトリのJavaScriptファイルを全て読み込む必要が出てくるので「//= require_tree .」を最後に追加する

app/assets/javascripts/application.js
//= require jquery
//= require rails-ujs
//= require_tree .

例えば sample1.js sample2.js sample3.jsというJavaScriptのファイルを
application.js直下に作成すれば
先ほどの記述で全て読み込んでくれるということです。


:file_folder: app
  :file_folder: assets
    :file_folder: config
    :file_folder: images
    :file_folder: javascripts
      :page_facing_up: application.js
      :page_facing_up: sample1.js
      :page_facing_up: sample2.js
      :page_facing_up: sample3.js
    :file_folder: stylesheets


 

jQueryが動くか確認する

動作テスト用に先ほどの例のようにapplication.jsの直下に
sample1.jsのみ実際にファイルを作成する。
次作成するファイル名が決まっていればその名前でも大丈夫。

sample1.jsのファイル内に以下を記述する。

app/assets/javascripts/sample1.js
$(function(){
  console.log("jQueryは正しく使えます")
});

記述できたら、
またページをリロードして「jQueryは正しく使えます」とコンソールに出ていれば正常に動いています。
スクリーンショット 2020-10-09 18.25.05.png

確認できたらsample1.jsの中身は削除する。
このファイルを使用する際は名前を変えてつかう。

 

Memo

jQueryを導入する際に、パッケージ管理システム「yarn」を使用しています。
hamlで記載しています。

パッケージ管理システムを利用しないとどのようになるのかまた調べたいです。

 

最後にひとこと

非同期通信を実装するために、
JavaScriptを読み込み、jQueryを導入する必要がありました。
JavaScriptはjQueryというライブラリを使用することでコードがシンプルにかけます。
上記で書いたコードはjQueryがなければかなり長くなります。

ライブラリのことやそれぞれの単語が何なのか。
また、yarnのようなパッケージ管理システムが何をしてくれているのか理解したいので、
また調べて記事にできたらと思います。

この後、非同期通信を実装するためにAjaxというのに入っていくのですが
こうして導入単体の記事にしたのは他に応用できるのかもという期待からです。
この段階から別の実装ができるのかはまだわかっていないけど、
もっと他のこともやってみたいと思いました。

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

CircleCIのシステムスペックをselenium dockerを利用する

今回が初投稿ですので、ミスや分かりにくいなどは多めに見てください

環境

・Ruby 2.7.1
・Rails 6.0.2.1

やりたいこと

・CircleCIでselnium dockerを利用してシステムテストを実行
・system js: trueをCircleCIでも利用できるようにする

docker-compose.yml

システムスペックで利用するseleniumのイメージを取得し、depens_onで連携とportの指定も忘れずに

docker-compose.yml
version: "3"
services:
  web:
    build: .
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    tty: true
    stdin_open: true
    depends_on:
      - db
      - chrome
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABE: db
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
    command: --default-authentication-plugin=mysql_native_password
  chrome:
    image: selenium/standalone-chrome-debug:latest
    ports:
      - "4444:4444"
volumes:
  mysql-data:
    driver: local

database.ymlを修正

このままではデータベースがdbコンテナが利用されないのでdatabase.ymlを修正します。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  database: myappp_production
  username: myapp
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>
$ docker-compose build
$ docker-compose up
$ docker-compose run web rake db:create

spec/rails_helper

テストに必要なGemを追加

Gemfile
group :test do
  gem 'capybara'
  gem 'selenium-webdriver'
  gem 'rspec-rails'
end

systemテストはselenium dockerを使うようにrails_specを変更する

rails_spec.rb
Capybara.register_driver :remote_chrome do |app|
  url = 'http://chrome:4444/wd/hub'
  caps = ::Selenium::WebDriver::Remote::Capabilities.chrome(
    'goog:chromeOptions' => {
      'args' => [
        'no-sandbox',
        'headless',
        'disable-gpu',
        'window-size=1680,1050'
      ]
    }
  )
  Capybara::Selenium::Driver.new(app, browser: :remote, url: url, desired_capabilities: caps)
end

RSpec.configure do |config|
  #省略

  config.before(:each, type: :system) do
    driven_by :rack_test
  end

  config.before(:each, type: :system, js: true) do
    driven_by :remote_chrome
    Capybara.server_host = IPSocket.getaddress(Socket.gethostname)
    Capybara.server_port = 4444
    Capybara.app_host = "http://#{Capybara.server_host}:#{Capybara.server_port}"
  end
end

これでローカルのシステムスペックは問題なく動くはずです

.circleci/config.yml

CircleCIの設定です
- run: mv ./config/database.yml.ci ./config/database.ymlでCI環境ではデータベースの設定を変更しています。
name: chromeと設定しておかないと
Errno::EADDRINUSE:Address already in use - bind(2) for "172.27.0.3" port 4444のエラーが発生する

config.yml
version: 2.1
jobs:
  build:
    docker:
      - image: circleci/ruby:2.7.1-node-browsers
          RAILS_ENV: test
          DB_HOST: 127.0.0.1
      - image: mysql:8.0
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: "true"
          MYSQL_ROOT_HOST: "127.0.0.1"
          MYSQL_DATABE: db
        command: --default-authentication-plugin=mysql_native_password
      - image: selenium/standalone-chrome-debug:latest
        name: chrome
    working_directory: ~/coffee
    steps:
      - checkout
      - run:
          name: bundleをインストール
          command: bundle check || bundle install --jobs=4
      - run:
          name: yarnを追加
          command: yarn install
      - run:
          name: webpackを追加
          command: bundle exec bin/webpack
      - run:
          name: rubocop
          command: bundle exec rubocop
      - run: mv ./config/database.yml.ci ./config/database.yml
      - run:
          name: データベースを作成
          command: bundle exec rails db:create || bundle exec rails db:migrate
      - run:
          name: rspec test
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
              circleci tests split --split-by=timings)"
            bundle exec rspec \
              --format RspecJunitFormatter \
              --out /tmp/test-results/rspec.xml \
              --format progress \
              $TEST_FILES
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

これでCircleCIでのrspec systemテストが正常に動くはずです

参考

Rails on DockerでRSpecのSystem testをSelenium Dockerを使ってやってみた。
既存のRails6アプリをDocker化しつつCircleCIでシステムスペックも実行できる環境を作る

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

【復習】RailsでWebアプリを作っている時にsyntax error, unexpected ')', expecting => ...]}%", "%#{params[:content]}%"]) ...

どうもshirakabaです。

今回はRailsの復習をしていきたいと思います。

以前オンラインスクールで作っていた作品を消してしまったので、もう一度復習も兼ねて作っていこうと思っています。

とはいっても今回書く内容は、アプリを作っていく過程で出たエラーを解決したよっ!ていうことを書いていきたいと思います。

余談なんですが、ずっと別のことに追われていて、自分の大切なことや本来の目的などや楽しかったことなどを忘れていたので、もう一度原点に戻ってそのタスクと、プログラミングの学習並行で行いながらやっていきたいと思います!

もう好き勝手学んでいきますよ!

※注意点
未経験で、今は独学で勉強中の初心者です。
あくまで学習したことをアウトプットする目的で書いております。
ですので、間違いなどがあるかと思いますが、その際は生暖かい目で見ていただければ幸いです。

解決したこと

何をしようとしたか?

検索機能を作成しようとしてコントローラーにsearchアクションを作ろうと以下の記述をしたところ下記エラーが出てしまいました。

def search
  if params[:content].present?
     @book = Book.where(user_id: current_user.id,['title LIKE ? OR author LIKE ? OR label LIKE ?',"%#{params[:content]}%", "%#{params[:content]}%", "%#{params[:content]}%"])
  else
    @book = Book.none
  end
end

エラー文

URLにアクセスするといつものエラー画面が...
SyntaxError
syntax error, unexpected ')', expecting => ...]}%", "%#{params[:content]}%"]) ... ^
@book = Book.where(user_id: current_user.id,['title LIKE ? OR author LIKE ? OR label LIKE ?',"%#{params[:content]}%", "%#{params[:content]}%", "%#{params[:content]}%"])

ターミナルだと
SyntaxError (/ファルダ名/フォルダ名/controllers/books_controller.rb:6: syntax error, unexpected ')', expecting =>
...]}%", "%#{params[:content]}%"])
... ^
):

解決方法

結論から言うとwhereを二つにしてつなげて書くことで解決しました。

@book = Book.where(user_id: current_user.id).where('title LIKE ? OR author LIKE ? OR label LIKE ?',"%#{params[:content]}%", "%#{params[:content]}%", "%#{params[:content]}%")

今回記述ミスによりエラーではありましたが、なかなか見つけるに苦労してしまいました...
ちゃんと原因を調べて落ち着いて考えればこんなにも時間がかかることはなかったかもしれないです。

復習とはいえ、まだまだ修行が足りないことを痛感させれられます。
それに、これ自体綺麗な書き方ではないのかもしれないので、もっとちゃんと調べようと思います。

最後に

今後はどんな些細なことでも、恥ずかしいことでもこうやってアウトプットして行けたらいいなと思っています。
それでは〜

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

既存のRails,MySQLアプリの開発環境とテスト環境にDockerを導入する

はじめに

タイトルの通り既存のRails,MySQLアプリの開発環境にDockerを導入します。
またrspecによるテストもDocker内で実行できるようにします。
既に同じような記事がたくさんありますが、結構エラー発生したので自分がうまくいった方法をまとめます。
間違いがあれば教えていただけると幸いです。

環境

  • MacOS
  • Ruby 2.6.5
  • Rails 5.2.3
  • MySQL 5.7

Railsのversionが6,MySQLのversionが8だとこの方法ではうまくいきません。

手順

  1. Dockerfile作成
  2. docker-compose.yml作成
  3. 既存のdatabase.ymlを変更
  4. 起動

1. Dockerfile作成

既存のプロジェクトのディレクトリにDockerfileを新規作成します。

ファイル構成
 test_app#既存プロジェクト
    ├──略
    ├── Dockerfile #作成
    ├── docker-compose.yml #作成
    ├── Gemfile
    ├── Gemfile.lock
    ├── .env #使用
    ├── config
         └──database.yml #変更

中身を記述していきます。
以下test_appは各々のアプリ名に合わせてください。

Dockerfile
FROM ruby:2.6.5

RUN apt-get update \
    && apt-get install -y --no-install-recommends nodejs mariadb-client build-essential \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

WORKDIR /test_app

COPY Gemfile /test_app/Gemfile
COPY Gemfile.lock /test_app/Gemfile.lock

RUN gem install bundler
RUN bundle install
COPY . /test_app
  • まずFROMでrubyのimageを取得。versionはご自身の環境に合わせます。
  • 次に必要なパッケージをinstallしています。mariadb-clientmysql-clientの記事もありますがエラーになったので注意。mysql-clientはmariadb-clientに統合されたようです。
  • 次は作業ディレクトリを指定。RUN mdirはなくてもWORKDIRで作成されるそうです。
  • 以下はgemfile等をローカルからコンテナ上にコピーしています。COPYADDとされている記事もありますがCOPY推奨らしいです。

2.docker-compose.yml作成

docker-compose.yml
version: '3'

services:
  db:
    image: mysql:5.7
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    ports: 
      - '3306:3306'
    environment:
      MYSQL_DATABASE: test_app_development
      MYSQL_ROOT_PASSWORD: ${DATABASE_DEV_PASSWORD}
      MYSQL_USER: ${DATABASE_DEV_USER}
      MYSQL_PASSWORD: ${DATABASE_DEV_PASSWORD}

  test-db:
    image: mysql:5.7
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    environment:
      MYSQL_DATABASE: test_app_test
      MYSQL_ROOT_PASSWORD: ${DATABASE_DEV_PASSWORD}
      MYSQL_USER: ${DATABASE_DEV_USER}
      MYSQL_PASSWORD: ${DATABASE_DEV_PASSWORD}
    ports:
      - '3307:3306'

  web:
    build:
      context: .
      dockerfile: Dockerfile
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    tty: true 
    stdin_open: true
    depends_on:
      - db
      - test-db
    ports:
      - "3000:3000" 
    volumes:
      - .:/test_app
      - bundle:/usr/local/bundle

volumes:
  bundle:

ポイントだけ書きます。

  • command行はdatabaseの文字コードをutf8に設定しています。これがないとdb:create時にエラーになりました。
  • enviromentではユーザー名やパスワードを設定しています。もちろん直接書いても動きますがここでは環境変数を展開しています。docker-composeファイルはデフォルトで.envファイルを読み込むようなので下記のように.envファイルに定義します。定義した環境変数は${}で展開できます。.envファイルはコミットしないように.gitignoreファイルに追加します。
.env
DATABASE_DEV_USER = 'hoge'
DATABASE_DEV_PASSWORD = 'password'

3. database.ymlを変更

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  charset: utf8
  pool: 5
  port: 3306

development:
  <<: *default
  database: test_app_development
  username: <%= ENV['DATABASE_DEV_USER'] %>
  password: <%= ENV['DATABASE_DEV_PASSWORD'] %>
  host : db

test:
  <<: *default
  database: test_app_test
  username: <%= ENV['DATABASE_DEV_USER'] %>
  password: <%= ENV['DATABASE_DEV_PASSWORD'] %>
  host : test-db

production:
  <<: *default
  database: <%= ENV['DB_NAME'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>
  host: <%= ENV['DB_HOSTNAME'] %>
  • usernameとpasswordはdocker-composeと同じものです。
  • hostで作成したdbコンテナに接続するように設定しています。
  • productionは各々の環境に合わせてください
  • 環境変数の管理にはdotenv-railsのgemを利用しています。

4. 起動

最後にdockerを起動できれば成功です。
1. docker-compose buildでimageを作成
2. docker-compose up -dでコンテナを立ち上げる。-dはバックグラウンドで起動するオプションです。
3. docker-compose run web rails db:createでデータベース作成

これでhttp://localhost:3000/ に表示されれば成功です。
あとはdb:migrateやdb:seedをすれば完了です。

参考記事

Dockerを使用して既存のRailsプロジェクト開発環境構築してみた(Rails+Mysql)
既存のRailsアプリをDockerコンテナで動かす方法+sequel proによるDBコンテナ可視化
Rails x Docker環境にテストDBを構築する

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

Rails書き方

# ダメな書き方
@incorrect_questions = session[:incorrect_question_ids].map { |n| Question.find(n.to_i) }

# なぜダメ?
# これだと件数ごとにQuestionを呼び出すクエリが発行されてしまって仮に100件あったら100件クエリが発行されてしまうのでこちらの方が良いかと。

# 良い書き方
@incorrect_questions = Question.where(id: session[:incorrect_question_ids].map(&:to_i))

Railsの考え方

・デフォルトに存在しないactionは別のcontrollerにした方が良い、というのがDHHの意見で、僕もそう思います。
・RESTとは?
・オブジェクトが自分自身の状態を持つのがオブジェクト指向の考え方の一つだよ。
・sessionを使うのは危険で、その人の状態をテーブルで用意しておけばいいのでは?
・Test◯◯という名前はrspecのファイルと勘違いされる可能性があるので避ける
・callback系(before_saveなど)は意図せずメソッドが走ったりするから、そこにロジックを書くのはあまり好ましくない。

# ダメな書き方
def index
  @questions = Question.all
end

#なぜダメ?
# 今の件数だと問題ないと思いますが、実際のサービスでallを使うとメモリが足りなくて落ちることがあるので気をつけた方が良いです。
基本的には動的に変わるデータ(例えばユーザーが追加するもの)はselect/pluckなどで必要なカラムを制限するように癖付けた方が良いかなと思います。
#ダメな書き方
<%= form_for(@question_similar, url: { controller: 'question_similar', action: 'create' }) do |f| %>

#ダメな理由
url: {〜〜〜} は xxx_path で書けそうですね
#ダメな書き方
 def cancel_save
    question_similars.each do |question_similar|
      if question_similar.similar_word == ""
        question_similar.delete
      end
    end
  end

#.blank使え
#トランザクション大丈夫?
#delete_all の方がパフォーマンスは良いと思います
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

質問箱のクローンをリプレイスする

これはなに?

GMOペパボさんが運営するオートスケール機能がついたコンテナサービスにRails製アプリをデプロイするまでのメモです。

アプリはこちらの質問箱のクローンをさらにクローンさせてもらっています。

作者の方はHerokuで運用されているみたいだったので、他の人が作ったアプリからRailsの組み方を学ばせてもらうとともに、Lolipop!マネージドクラウド(以下マネクラ)へのデプロイの練習を目的として進めてみました。

大元の情報

事前の準備

  • ロリポップ!マネージドクラウドの会員登録とssh,各種鍵の登録までしておく
  • rbenvを入れていろんなバージョンのrubyを使ったリポジトリを扱えるようにしておく
  • git や エディタ を設定しておく

マネージドクラウド側の設定

  • プロジェクト(専用コンテナ)をつくる
  • コンテナにsshができるか確かめる

なんだかんだ一つのコンテナあたり10日間無料で使えるのは太っ腹です。
難しい設定をしらなくても、チェックツールさえ通って入ればコンテナ上で本番環境に対してdb:migrate かけてくれたりするのも魅力ですね。

macに当該リポジトリをcloneしてくる

git clone git@github.com:seiyatakahashi/peing-questionbox-clone.git

ローカルリポジトリのRuby および Railsの バージョンをロリポップと揃える

マネージドクラウドではRubyが2.5.8、railsが5.2だったので、ローカルでruby -v rails -vしてバージョンを確かめました。
ここでバージョンがあって揃ってなかったため、
rbenv install 2.5.8
および、
rbenv local 2.5.8 && rbenv rehash してあげてバージョンを揃えてあげました。
(railsは変えた記憶(history)がない...)

チェックツール

公式で用意されているチェックツールを入れましょう。(github)

gem install lolipop-mc-starter-rails

ここでRailsのプロジェクトをマネクラにPushするための必要事項を適宜チェックしてくれる(スゴイ)。

私の場合はここでdatabase.ymlがなんか変だぞと教えたもらったので、

./config/database.yml
の内容をみてみます。

database.tmlの内容
# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
#
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: db/test.sqlite3

production:
  database: my_database_production
  adapter: postgresql
  encoding: unicode
  pool: 5

ここでRailsの各環境におけるSQLの選択ができます。
クローン元のリポジトリでは、Herokuのproduction環境のPostgreSQLに合わせてあるので、
マネクラの本番環境(production)のMySQLに合わせないといけないですね。

というわけでこんな感じで書き換えます。

database.ymlを変更
production:
  <<: *default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV["MC_DB_USERNAME"] %>
  password: <%= ENV["MC_DB_PASSWORD"] %>
  database: <%= ENV["MC_DATABASE"] %>
  host:     <%= ENV["MC_DB_HOSTNAME"] %>

MC_DB_USERNAME などなどは、ブラウザからマネクラのコントロール画面を開いて環境変数として設定してあげます。
もちろんそのまま直書きもできますが、危険なのでやめましょう。
(ちなみにローカル環境でも直書きしたくないよ〜ってひとはenvdir とかで設定してあげるといいかも)

gem install mysql2 ローカルリポジトリにgemを入れてあげて、
./gemfilegem mysql2 と追記。

新たにgemを入れたので bundle install してあげて、これでSQL周りは大丈夫なはず。

マネクラの環境変数を設定していく。

続いてブラウザでマネクラのコントロールパネルを開きつつ、マネクラ側の環境変数を設定していきます。
上記MC_DB_USERNAMEなどを読み込むようにします。

今回は他の人が作成したRailsのプロジェクトを使っているので、./config/master_keyを作り直しました。

EDITOR="vi" bin/rails credentials:editを打ち込むと上書き作成されます。

これをRAILS_MASTER_KEYとしてマネクラの環境変数に登録。

また、ローカルで、mc-rails databaseしてデータベースのURLを作成します。
データベース名、ユーザ名、パスワードの入力を求められるので、マネクラのコンソールにあるものをそれぞれ入力すると、
URLが表示されるのでこれをDATABASE_URLとして同様に環境変数として設定。

また、公式の手順に則り、RAILS_SERVE_STATIC_FILESも環境変数に入力し、値としてtrueを与えておきましょう。

Twitterの各種認可キーを取得する。

Tiwtterのデベロッパー向けサイトにいき、APIキーの発行を行いました。

Developer portal に移動し、会員登録をしていなければ、会員登録およびSign in後、右上のアカウント名のメニューより作成したAPIキー一覧のページに飛びます。

Create an APPs の項目を適宜埋めてAPIキーを作成したら、Details > Keys and tokensより、
- API Key
- API serect key
- Access token
- Access token secret
を手元に控えておきます。

これをそれぞれ、

環境変数名
API_KEY "API Key"
API_SECRET "API serect key"
TOKEN "Access token"
SECRET "Access token secret"

としてマネクラに登録しておきます。

なお、クローン元のアプリではさらに、

  • APP_NAME="アプリの名前"
  • APP_NAME_EN="アプリの英語の表記名"
  • CURRENT="自分ののツイッターのID"
  • DESCRIPTION="サイトの情報"
  • KEYWORDS="サイトのキーワード"
  • GOOGLE_ANALYTICS="Google アナリティクスのID"

といった情報もコンテナの環境変数として登録できるように設定されています。(README.mdより)

マネクラにPush

改めて

git add .
git commit
mc-rails check

なにも怒られななかったはずなので、これでローカルの
git push lolipop master
してマネクラにpush。

無事にデプロイできました?

リモートリポジトリ(コンテナ)からエラーを拾う。

上記で無事に、といってはいますが、実際には当初、Twitterアカウントでの認証画面への遷移の際にエラーが吐かれていました。
そこで、リモートリポジトリの中に何かエラーログはないか探索してみました。

リモートリポジトリは以下のようになっていて、

僕のリモートリポジトリのホームディレクトリ:~$ ls -a
.   .bash_history  .cache    .ssh  .viminfo      1597471676557237  projrepo
..  .bundle    .lolipop  .vim  0000000000000000  current           shared

このcurrentの中がPushしたRailsのプロジェクトになっています。
また、currentディレクトリ内にlogディレクトリが存在しており、この中にコンテナのログが蓄積されていくことになります。

Haconiwaのログが出るのでこれを掘る

logの中身は

gitpush.log
production.log
{プロジェクト名}.lolipop.io-{ハッシュ値} みたいなディレクトリがたくさん

という感じでした。

ややエスパーではありますが、ここでduして重そうなログが格納してあるディレクトリを探しました。
普通のディレクトリの容量が、4KBのところ、40KBくらいのディレクトリを見つけたのでここをcatしてみると、

Started GET "/auth/twitter"のあとに、

...OAuth::Unauthorized (401 Authorization Required)...

みたいな怒られが発生しているのを発見。

どうやらTwitter APIの認可がうまくいってないっぽい。参考

なので再度Callback用のURLなどTwitterのDevサイトにバッチリ登録し直してみるとうまく画面遷移するようになりました。

ちなみにHaconiwaはRuby製のコンテナです。

おわりに

VPSとか手持ちのオンプレ環境にRailsを動かしたあとだったこともあって、とても手軽にアプリを作成できるようになるのはめちゃくちゃ体験としてよかったです。

また、他の人の作った質問箱様アプリを触ってみて、「僕だったらこの機能とこの機能に絞って作るかも!」とか、「テーブル構造はどうなってるんだろう」みたいな感想が浮かんだのもいい収穫だった様に思います。

Dockerもですが、デプロイ先としてコンテナを設けて置くのはちょっとしたアプリをシャッと作りたいときに便利そうですよね。

先に触れたHaconiwaに関連する宣伝ではありますが、mRubyの本が2020年11月に刊行されるみたいです。買いましょう。

おわり。

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

[Rails] Couldn't find with 'id'=:id でupdateアクションが実行されない

解決方法

@を消すと解消される

修正前
def update
@gear = find_gear_by_id
@gear.update(gear_params)
redirect_to gears_path
flash[:notice] = "編集が完了しました"
end


修正後
def update
gear = find_gear_by_id
gear.update(gear_params)
redirect_to gears_path
flash[:notice] = "編集が完了しました"
end

詳しい理由がわからない。。。

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

Ruby on rails学習記録 -2020.10.09

ログイン時だけの機能を持つ掲示板

1行掲示板の作成
$ cd bbs_users
$ rails g scaffold article user_id:integer content:string
$ rails db:migrate

初期データの投入

db/seeds.rb
Article.create(user_id: 1, content: 'hello world')
Article.create(user_id: 1, content: 'hello paiza')
Article.create(user_id: 2, content: '世界の皆さん、こんにちは')

データベースに反映

$ rails db:seed

ログイン時に、特定のアクションだけ実行できるようにする

articles_controller.rb
before_action :authenticate_user!, only: [:new, :create, :edit, :update, :destroy]
before_action :set_article, only: [:show, :edit, :update, :destroy]

ArticlesモデルとUserモデルの関連付け

model/article.rb
class Article < ApplicationRecord
    belongs_to :user
end

投稿者のメールアドレスの表示

views/articles/index.html.erb
<table>
  <thead>
    <tr>
      <th>User</th>
      <th>Content</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @articles.each do |article| %>
      <tr>
        <td><%= article.user.email %></td>
        <td><%= article.content %></td>
        <td><%= link_to 'Show', article %></td>
        <td><%= link_to 'Edit', edit_article_path(article) %></td>
        <td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

ナビゲーションを共通で表示

app/views/layouts/application.html.erb
<body>
  <% if user_signed_in? %>
    Logged in as <strong><%= current_user.email %></strong>.
    <%= link_to "Settings", edit_user_registration_path %> |
    <%= link_to "Logout", destroy_user_session_path, method: :delete %>
  <% else %>
    <%= link_to "Sign up", new_user_registration_path, :class => 'navbar-link' %> |
    <%= link_to "Login", new_user_session_path, :class => 'navbar-link' %>
  <% end %>
  <p class="notice"><%= notice %></p>
  <p class="alert"><%= alert %></p>
  <%= yield %>
</body>

Userモデルにカラムを追加
$ rails g migration AddNameToUser name:string
$ rails db:migrate

サインアップ画面に「name」カラムを追加

app/views/devise/registrations/new.html.erb
<div class="field">
  <%= f.label :name %><br />
  <%= f.text_field :name %>
</div>

ユーザー情報の変更画面に「name」カラムを追加

app/views/devise/registrations/edit.html.erb
<div class="field">
  <%= f.label :name %><br />
  <%= f.text_field :name %>
</div>

Userモデルのユーザー名の変更

コントーローラで、nameカラムを保存

app/controllers/application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?

protected

def configure_permitted_parameters
  devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
  devise_parameter_sanitizer.permit(:account_update, keys: [:name])
end

ナビゲーションのログイン情報に、ユーザー名を表示

app/views/layouts/application.html.erb
<% if user_signed_in? %>
  Logged in as <strong><%= current_user.name %></strong>.
  <%= link_to "Settings", edit_user_registration_path, :class => "navbar-link" %> |
  <%= link_to "Logout", destroy_user_session_path, method: :delete, :class => "navbar-link" %>
<% else %>
  <%= link_to "Sign up", new_user_registration_path, :class => 'navbar-link' %> |
  <%= link_to "Login", new_user_session_path, :class => 'navbar-link' %>
<% end %>

投稿一覧に、nameカラムを表示

views/articles/index.erb.html
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Content</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @articles.each do |article| %>
      <tr>
        <td><%= article.user.name %></td>
        <td><%= article.content %></td>
        <td><%= link_to 'Show', article %></td>
        <td><%= link_to 'Edit', edit_article_path(article) %></td>
        <td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

投稿の詳細画面に、nameカラムを表示

views/articles/show.erb.html
<p>
  <strong>User:</strong>
  <%= @article.user.name %>
</p>

新規投稿フォームを修正して、user_idを削除

app/views/articles/_form.html.erb
<%= form_with(model: article, local: true) do |form| %>
  <% if article.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2>

      <ul>
      <% article.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :content %>
    <%= form.text_field :content, id: :article_content %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

createメソッドの修正

app/controllers/articles_controller.rb
# POST /articles
# POST /articles.json
def create
  @article = Article.new(article_params)
  @article.user_id = current_user.id

updateメソッドの修正

app/controllers/articles_controller.rb
def update
    if @article.user_id == current_user.id
      respond_to do |format|
        if @article.update(article_params)
          format.html { redirect_to @article, notice: 'Article was successfully updated.' }
          format.json { render :show, status: :ok, location: @article }
        else
          format.html { render :edit }
          format.json { render json: @article.errors, status: :unprocessable_entity }
        end
      end
    else
        redirect_to @article, notice: "You don't have permission."
    end
end

destroyメソッドの修正

app/controllers/articles_controller.rb
def destroy
  if @article.user_id == current_user.id
    @article.destroy
    msg = "Article was successfully destroyed."
  else
    msg = "You don't have permission."
  end
  respond_to do |format|
    format.html { redirect_to articles_url, notice: msg }
    format.json { head :no_content }
  end
end

updateアクションの修正

app/controllers/articles_controller.rb
def update
    if @article.user_id == current_user.id
      respond_to do |format|
        if @article.update(article_params)
          format.html { redirect_to @article, notice: 'Article was successfully updated.' }
          format.json { render :show, status: :ok, location: @article }
        else
          format.html { render :edit }
          format.json { render json: @article.errors, status: :unprocessable_entity }
        end
      end
    else
        redirect_to @article, notice: "You don't have permission."
    end
end

destroyアクションを修正する

app/controllers/articles_controller.rb
def destroy
  if @article.user_id == current_user.id
    @article.destroy
    msg = "Article was successfully destroyed."
  else
    msg = "You don't have permission."
  end
  respond_to do |format|
    format.html { redirect_to articles_url, notice: msg }
    format.json { head :no_content }
  end
end

投稿一覧の表示

app/views/articles/index.html.erb
<% if user_signed_in? && article.user_id == current_user.id %>
    <td><%= link_to 'Edit', edit_article_path(article) %></td>
    <td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% end %>

詳細画面の修正

app/views/articles/show.html.erb
<% if user_signed_in? && @article.user_id == current_user.id %>
    <%= link_to 'Edit', edit_article_path(@article) %> |
<% end %>
<%= link_to 'Back', articles_path %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails simple formでbooleanのvalueをjQueryでgetする方法

jqueryでcheckされたかをどうやって実装するか悩んで調べましたが、あまり情報がなく自分で実装してみました

# rails form, idは"testBool"とします
= f.input :test_bool, as: :boolean

// jquery
$('#testBool').on('click', function() {
  console.log($(this)[0]['checked'])
})

-> 
false
true

この実装方法でいけば動的にtrue, falseを取得することができるのでjs側での制御がかなり楽になります

参考記事

なし

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

【Ruby on Rails】ページング機能導入

目標

ページング機能を作成し、
一覧表示時の画像読み込み速度低下を防ぐ

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

前提

※ ▶◯◯ を選択すると、説明等が出てきますので、
  よくわからない場合の参考にしていただければと思います。

今回はgem 'kaminari'を導入し、ページング機能実装します。

kaminari導入までの流れ

Gemfile
gem 'kaminari','~> 1.2.1'
ターミナル
$ bundle install
$ rails g kaminari:config
$ rails g kaminari:views default

これで導入は完成です。

使い方

個人的によく使うものを紹介します。
https://github.com/kaminari/kaminari
こちらに詳しい使い方が記載されておりますので、
興味のある方はこちらをみてください。
ただし、全て英語です。

基本的なページング

ページングしたい場所に以下を設置。
この場所に1ページ目や2ページ、次へリンクも出てきます。

app/views/homes/index.html.erb
<%= paginate @posts %>

コントローラーをこのように記述することでページングが実装できます。

app/controllers/homes_controller.rb
@posts = posts.page(params[:page])

補足【page(params[:page])】
page(params[:page]) ≒ all と思って頂いて大丈夫です。

ページングの並び替え

コントローラーの記述に書きを追加することで逆の順番にすることが可能。

app/controllers/homes_controller.rb
@posts = posts.page(params[:page]).reverse_order

1ページあたりの表示件数を指定

config/initializers/kaminari_config.rb
  Kaminari.configure do |config|
    config.default_per_page = 5 # この数字でkaminariを使用したすべてのページの1ページあたりの表示上限件数を指定
  end

個別で表示1ページあたりの表示件数を指定する場合

app/controllers/homes_controller.rb
@posts = posts.page(params[:page]).per(10)

補足【表示の優先順位】
優先順位はkaminari_config.rbの数字が優先されるため、
kaminari_config.rb >= homes_controller.rb
このような数字の優劣をつけてください。

表示を変更する

nextやlastなどを変更したい場合、

1 gem 'rails-i18n'を導入
2 config/locals/ja.ymlファイルを作成
3 ja.ymlを編集

上記を行えばOKです。
【ja.yamlの参考】

ja.yaml
ja
  views:
    pagination:
      first: "≪"
      previous: "<"
      next: ">"
      last: "≫"

配列に対して適用する場合

上記方法で実装する際に、値が配列の場合は表示されないため、
下記のように記述する必要があります。

app/controllers/homes_controller.rb
@posts = Kaminari.paginate_array(配列).page(params[:page])

実際の記述:【Ruby on Rails】ランキング表示(合計、平均値)

その他機能

【Rails】自動で次のページへ!!jscrollによるページネーションの無限スクロール

他にも多くあるので興味のある方はぜひ調べてみてください。

まとめ

導入自体はそこまで難しくないものの、ページング機能がないと
サイトが成長したときに読み込みが遅く使い物にならないので、
なにかしらのページング機能は必須です。

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

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

herokuでアプリのサーバにsshログイン

1 アプリのディレクトリに移動&&herokuログイン

$ cd app_name
$ heroku login --interactive

2 ssh接続したいアプリを指定

$ heroku git:remote --app app_name

3 sshでサーバに接続

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

【0からDockerに挑戦】②DockerとDocker-composeを使ってRuby on Rails・MYSQLの開発環境をつくる part1

背景

未経験から自社開発系企業の就職を目指します。
良質なポートフォリオ作成のためDockerを勉強することにしました。

知識レベルとしては、Ruby on railsを使って簡単なアプリケーション開発、gitを使ったバージョン管理、herokuを使ってデプロイできるレベルです。
またDockerを学ぶ上で必要とされるlinuxやネットワーク関連の勉強はほぼしていないです。
同じくらいのレベルでこれからDockerに挑戦してみようと思っている方に向けて少しでも役に立てればと思います。

最終目標

・Dockerを使って0からRuby on Rails環境が構築できるようになること。
※複数回に渡ってdockerの記事を書く予定です。今回はその第2弾「DockerとDocker-composeを使ってRuby on Rails・MYSQLの開発環境をつくる」です。

DockerとDocker-composeを使ってRuby on Rails・MYSQLの開発環境をつくる

今回の環境と条件

・Dockerを使って環境構築を行う。(コンテナ上でサーバーを起動し、localhost:3000でrailsの初期画面を映すまでがゴール)。
・railsのバージョンは6.0.0
・rubyのバージョンは2.6
・データベースはmysql(今回はバージョン指定していない)
・railsとデータベースは別々にコンテナをたてます。
・基本的に他サイトからの無意味なコピペはしない。すべてのコードを理解した上で実装する。

手順

今回は下記手順で実施したいと思います。
 ①ローカルPC上に必要なディレクトリ・ファイルをつくる
 ②そのうちDockerfile、docker-compose.yml、Gemfileに必要な記述を書く
 ③docker-composeをビルド・実行させる
 ④コンテナ上でrails newを実行し必要なファイルを作成する。
 ⑤コンテナ上でデータを作成・起動する

途中でエラーにもたくさん悩まされたので、そのエラーについても説明します。
私と同じように初めてDockerで環境構築しようとしている方にとって少しでも役にたてれば幸いです。それでは早速実施します。

①ローカルPC上に必要なディレクトリ・ファイルをつくる

今回はデスクトップ上にディレクトリをつくりました。構成は下記になります。

Desktop/
├ test-product/
    └ Dockerfile
    ├ docker-compose.yml
    ├ Gemfile
    ├ Gemfile.lock

ファイルの説明をします。DockerfileとはDockerコンテナの定義や何をするかを記述します。Dockerfileがコンテナの中身を決めるものだとすると、docker-compose.ymlはコンテナを外側から見たときの挙動や複数コンテナがある場合はコンテナ同士の関係性なども記述します。Gemfileはrailsをやっている方ならわかるかと思いますが、ライブラリを管理するパッケージです。なぜGemfileだけあらかじめ作っておくかというと、railsのバージョンを指定するためです。DockerfileのFROMにはrubyのイメージを指定していますが、Railsのバージョンはありません。そのためあらかじめGemfileにRailsのバージョンを指定することで、Dockerfileでbundle installをしたときにrailsのバージョンに合わせたライブラリがインストールされます。基本的にはDockerfileでのイメージは言語を使用し、同じディレクトリ内にフレームワーク用のパッケージを用意するやり方が主流だといわれています。 Gemfile.lockはGemfileと合わせて必要なファイルなので用意したと捉えてください。

②そのうちDockerfile、docker-compose.yml、Gemfileに必要な記述を書く

Gemfile.lockは空のままで大丈夫です。それ以外の3つは下記のように編集しましょう。
※実は実行した際たくさんのエラーに出会っています。下記に記すのは最終的に問題のなかったコードです。

■コードの説明

Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6.0.0'

こちらはrails勉強した人なら問題ないと思います。今回はrails6.0.0を使います。

Dockerfile
FROM ruby:2.6
RUN apt-get update && apt-get install -y \
      build-essential \
      libpq-dev \
      nodejs \
      mariadb-client &&\
      curl -o- -L https://yarnpkg.com/install.sh | bash
WORKDIR /test-product
COPY Gemfile Gemfile.lock /test-product/
RUN bundle install

上から順に簡単に説明します。
- FROMでrubyのイメージを取得しています。使う予定のRailsのバージョンにも気をつけてrubyのバージョンも指定してください。
- RUNで必要なライブラリをインストールしています。ちなみにapt-getはubuntuで使われるコマンドです。installの前にupdateするのは、古いバージョンをインストールする可能性があるためです。また、インストールする内容は言語によって異なりますが、rubyの場合は基本上記で大丈夫です。
- WORLDIRでコンテナ内の作業場所を移します。今回の場合/test-productはコンテナ内にないため新たに作成されます
- COPYでローカルのGemfileとGemfileをコンテナの/test-productにコピーします。
- 最後にRUNでbundle installを実行します。これで/test-productにあるGemfileが反映されるようになります。

docker-compose.yml
version: '3'

volumes:
    db-data:

services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - '.:/test-product'
    environment: 
      - 'DATABASE_PASSWORD=mysql'
    tty: true
    stdin_open: true
    depends_on:
      - db
    links:
      - db

  db:
    image: mariadb
    volumes:
      - 'db-data:/var/lib/mysql/data'
    environment: 
      MYSQL_DATABASE: root
      MYSQL_ROOT_PASSWORD: mysql

こちらも上から順に説明します。
- versionはdocker-composeのバージョンです。基本3で問題ありません
- volumesはコンテナ内のデータをローカルに保存するための記述です。コンテナは削除されるとデータもまるごと消えてしまします。そのためvolumesを書くことでローカル内の見えないところでコンテナのデータを保存してくれます。
- serviceが具体的なコンテナの挙動を指定します。serviceはweb(railsのコンテナ)とdb(mysqlのコンテナ)に分かれています。
- buildはローカルでDockerfileがある場所を指定しています。今回はカレントディレクトリです。
- portはlocalhost:3000に問い合わせたら、コンテナの3000番が対応するという結びつけです。
- enviromentで環境変数を埋め込みます。今回はデータベースのパスワードをコンテナ内に埋め込みます。(rails newをしたらdatabase.ymlの変更の必要あり)
- ttyとstdin_opnをtrueにするとコンテナ内に入って操作ができるようになります。
- depend onとlinkでwebコンテナとdbコンテナを結びつけます。
- db:imageでデータベースのイメージを取得しています。
- volumesでコンテナのデータをローカルに保存する場所を指定します。データーベース種類によって決まりがあります。mysqlの際はこれで大丈夫です。

■エラーポイント

エラーが発生し困ったポイントです。Dockerfileやdocker-compose.ymlはエラー起こりやすいので気をつけましょう。

Dockerfile
(省略)
RUN apt-get update && apt-get install -y \
    ・
    ・
      mariadb-client &&\
      curl -o- -L https://yarnpkg.com/install.sh | bash
    ・
    ・
(省略)

上記は2点あります。まずmariadb-clientの箇所です。mysqlを使うのにmariadbを指定しています。これはコンテナ内で「rails s」をしたところ、「mysql-clientはありません」とエラーがでました。調べてみると「mariadb]と記載するとそのエラーが解消されるようです。
2つめはyarnに関してです。rails6.0以降はwebpackerのインストールが必須なのですが、その際にyarnをインストールする必要があります。「apt-get install yarn」でもyarnのインストールはできるのですが、webpackerをインストールするにはyarnが最新のバージョンでないといけないためエラーが起こります。そのため「curl・・・・」でyarnの最新バージョンをインストールしています。

[参考]
docker-compose buildするときにbundle installやmysql-clientでコケた話 @aseanchild1400
yarn公式ーーインストール

docker-compose.yml
db:
    image: mariadb
    volumes:
      - 'db-data:/var/lib/mysql/data'
    environment: 
      MYSQL_DATABASE: root
      MYSQL_ROOT_PASSWORD: mysql

こちらに関しては「docker-compose up」を実行してもコンテナが起動しない場合があります。その場合ターミナルで「docker-compose logs」を入力してみましょう!そうすると原因がわかるのですが、今回はmysqlのnameとpasswordがないとエラーがでたので、enviroment以下追加しました。(※こちらのやり方関しては最後煮汁してあるudemyのDocker講座の方を参照しています)。
またこちらのimageもmysqlだとエラーが起こるのでmariadbにしています。




長くなってしまったのでここで一旦切ります。③以降は次回の記事に書いていきます。

まとめ

railsだとyarnとwebpackerあたりがエラー起きやすいです。あとはmysqlなどdb関連もエラー起きやすいので注意が必要です。
実際に探り探り環境構築していくと、docker実行時にたくさんエラーが起きますが、1個1個調べていけば解決できるでしょう。
dockerもまだまだ奥深いですが、だんだん面白くなってきたので頑張りたいです!

参考になった教材

「米国AI開発者がゼロから教えるDocker講座」 
https://www.udemy.com/course/aidocker/
→初心者がDockerを学ぶならこれ!!!めちゃくちゃわかりやすい!!!linuxの基礎からDockerでの環境構築を網羅しています。
今回の環境構築もこの教材をベースにしています(もちろんrailsのバージョンとかdbなど自分なりに変えているので同じではないです)
もしDocker全然わかないという人はこの講座をぜひ買ってみてください。

「いまさらだけどDockerに入門したので分かりやすくまとめてみた」 
https://qiita.com/gold-kou/items/44860fbda1a34a001fc1
→すごく細かく網羅しているのでとてもわからなくなったらよく見ています。リファラル的によくみさせていただいてます。

「Docker/Kubernetes 実践コンテナ開発入門 --著 山田 明憲」 
→実践的な書籍、あまりにも初学者だと少しむずかしいかも。 → 基礎覚え得たあとに見返したら非常に良かったです!

「たった1日で基本が身に付く! Docker/Kubernetes超入門 --著」
→初学者が書籍で学びたい場合はこれがとっかかりやすい。

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

【0からDockerに挑戦】②DockerとDocker-composeを使ってRuby on Rails・MYSQLの開発環境をつくる (part1)

背景

未経験から自社開発系企業の就職を目指します。
良質なポートフォリオ作成のためDockerを勉強することにしました。

知識レベルとしては、Ruby on railsを使って簡単なアプリケーション開発、gitを使ったバージョン管理、herokuを使ってデプロイできるレベルです。
またDockerを学ぶ上で必要とされるlinuxやネットワーク関連の勉強はほぼしていないです。
同じくらいのレベルでこれからDockerに挑戦してみようと思っている方に向けて少しでも役に立てればと思います。

最終目標

・Dockerを使って0からRuby on Rails環境が構築できるようになること。
※複数回に渡ってdockerの記事を書く予定です。今回はその第2弾「DockerとDocker-composeを使ってRuby on Rails・MYSQLの開発環境をつくる」です。

DockerとDocker-composeを使ってRuby on Rails・MYSQLの開発環境をつくる

今回の環境と条件

・Dockerを使って環境構築を行う。(コンテナ上でサーバーを起動し、localhost:3000でrailsの初期画面を映すまでがゴール)。
・railsのバージョンは6.0.0
・rubyのバージョンは2.6
・データベースはmysql(今回はバージョン指定していない)
・railsとデータベースは別々にコンテナをたてます。
・基本的に他サイトからの無意味なコピペはしない。すべてのコードを理解した上で実装する。

手順

今回は下記手順で実施したいと思います。
 ①ローカルPC上に必要なディレクトリ・ファイルをつくる
 ②そのうちDockerfile、docker-compose.yml、Gemfileに必要な記述を書く
 ③docker-composeをビルド・実行させる
 ④コンテナ上でrails newを実行し必要なファイルを作成する。
 ⑤コンテナ上でデータを作成・起動する

途中でエラーにもたくさん悩まされたので、そのエラーについても説明します。
私と同じように初めてDockerで環境構築しようとしている方にとって少しでも役にたてれば幸いです。それでは早速実施します。

①ローカルPC上に必要なディレクトリ・ファイルをつくる

今回はデスクトップ上にディレクトリをつくりました。構成は下記になります。

Desktop/
├ test-product/
    └ Dockerfile
    ├ docker-compose.yml
    ├ Gemfile
    ├ Gemfile.lock

ファイルの説明をします。DockerfileとはDockerコンテナの定義や何をするかを記述します。Dockerfileがコンテナの中身を決めるものだとすると、docker-compose.ymlはコンテナを外側から見たときの挙動や複数コンテナがある場合はコンテナ同士の関係性なども記述します。Gemfileはrailsをやっている方ならわかるかと思いますが、ライブラリを管理するパッケージです。なぜGemfileだけあらかじめ作っておくかというと、railsのバージョンを指定するためです。DockerfileのFROMにはrubyのイメージを指定していますが、Railsのバージョンはありません。そのためあらかじめGemfileにRailsのバージョンを指定することで、Dockerfileでbundle installをしたときにrailsのバージョンに合わせたライブラリがインストールされます。基本的にはDockerfileでのイメージは言語を使用し、同じディレクトリ内にフレームワーク用のパッケージを用意するやり方が主流だといわれています。 Gemfile.lockはGemfileと合わせて必要なファイルなので用意したと捉えてください。

②そのうちDockerfile、docker-compose.yml、Gemfileに必要な記述を書く

Gemfile.lockは空のままで大丈夫です。それ以外の3つは下記のように編集しましょう。
※実は実行した際たくさんのエラーに出会っています。下記に記すのは最終的に問題のなかったコードです。

■コードの説明

Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6.0.0'

こちらはrails勉強した人なら問題ないと思います。今回はrails6.0.0を使います。

Dockerfile
FROM ruby:2.6
RUN apt-get update && apt-get install -y \
      build-essential \
      libpq-dev \
      nodejs \
      mariadb-client &&\
      curl -o- -L https://yarnpkg.com/install.sh | bash
WORKDIR /test-product
COPY Gemfile Gemfile.lock /test-product/
RUN bundle install

上から順に簡単に説明します。
- FROMでrubyのイメージを取得しています。使う予定のRailsのバージョンにも気をつけてrubyのバージョンも指定してください。
- RUNで必要なライブラリをインストールしています。ちなみにapt-getはubuntuで使われるコマンドです。installの前にupdateするのは、古いバージョンをインストールする可能性があるためです。また、インストールする内容は言語によって異なりますが、rubyの場合は基本上記で大丈夫です。
- WORLDIRでコンテナ内の作業場所を移します。今回の場合/test-productはコンテナ内にないため新たに作成されます
- COPYでローカルのGemfileとGemfileをコンテナの/test-productにコピーします。
- 最後にRUNでbundle installを実行します。これで/test-productにあるGemfileが反映されるようになります。

docker-compose.yml
version: '3'

volumes:
    db-data:

services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - '.:/test-product'
    environment: 
      - 'DATABASE_PASSWORD=mysql'
    tty: true
    stdin_open: true
    depends_on:
      - db
    links:
      - db

  db:
    image: mariadb
    volumes:
      - 'db-data:/var/lib/mysql/data'
    environment: 
      MYSQL_DATABASE: root
      MYSQL_ROOT_PASSWORD: mysql

こちらも上から順に説明します。
- versionはdocker-composeのバージョンです。基本3で問題ありません
- volumesはコンテナ内のデータをローカルに保存するための記述です。コンテナは削除されるとデータもまるごと消えてしまします。そのためvolumesを書くことでローカル内の見えないところでコンテナのデータを保存してくれます。
- serviceが具体的なコンテナの挙動を指定します。serviceはweb(railsのコンテナ)とdb(mysqlのコンテナ)に分かれています。
- buildはローカルでDockerfileがある場所を指定しています。今回はカレントディレクトリです。
- portはlocalhost:3000に問い合わせたら、コンテナの3000番が対応するという結びつけです。
- enviromentで環境変数を埋め込みます。今回はデータベースのパスワードをコンテナ内に埋め込みます。(rails newをしたらdatabase.ymlの変更の必要あり)
- ttyとstdin_opnをtrueにするとコンテナ内に入って操作ができるようになります。
- depend onとlinkでwebコンテナとdbコンテナを結びつけます。
- db:imageでデータベースのイメージを取得しています。
- volumesでコンテナのデータをローカルに保存する場所を指定します。データーベース種類によって決まりがあります。mysqlの際はこれで大丈夫です。

■エラーポイント

エラーが発生し困ったポイントです。Dockerfileやdocker-compose.ymlはエラー起こりやすいので気をつけましょう。

Dockerfile
(省略)
RUN apt-get update && apt-get install -y \
    ・
    ・
      mariadb-client &&\
      curl -o- -L https://yarnpkg.com/install.sh | bash
    ・
    ・
(省略)

上記は2点あります。まずmariadb-clientの箇所です。mysqlを使うのにmariadbを指定しています。これはコンテナ内で「rails s」をしたところ、「mysql-clientはありません」とエラーがでました。調べてみると「mariadb]と記載するとそのエラーが解消されるようです。
2つめはyarnに関してです。rails6.0以降はwebpackerのインストールが必須なのですが、その際にyarnをインストールする必要があります。「apt-get install yarn」でもyarnのインストールはできるのですが、webpackerをインストールするにはyarnが最新のバージョンでないといけないためエラーが起こります。そのため「curl・・・・」でyarnの最新バージョンをインストールしています。

[参考]
「docker-compose buildするときにbundle installやmysql-clientでコケた話 @aseanchild1400」
「yarn公式ーーインストール」

docker-compose.yml
db:
    image: mariadb
    volumes:
      - 'db-data:/var/lib/mysql/data'
    environment: 
      MYSQL_DATABASE: root
      MYSQL_ROOT_PASSWORD: mysql

こちらに関しては「docker-compose up」を実行してもコンテナが起動しない場合があります。その場合ターミナルで「docker-compose logs」を入力してみましょう!そうすると原因がわかるのですが、今回はmysqlのnameとpasswordがないとエラーがでたので、enviroment以下追加しました。(※こちらのやり方関しては最後煮汁してあるudemyのDocker講座の方を参照しています)。
またこちらのimageもmysqlだとエラーが起こるのでmariadbにしています。




長くなってしまったのでここで一旦切ります。③以降は次回の記事に書いていきます。

まとめ

railsだとyarnとwebpackerあたりがエラー起きやすいです。あとはmysqlなどdb関連もエラー起きやすいので注意が必要です。実際に探り探り環境構築していくと、docker実行時にたくさんエラーが起きますが、1個1個調べていけば解決できるでしょう。
dockerもまだまだ奥深いですが、だんだん面白くなってきたので頑張りたいです!

参考になった教材

「米国AI開発者がゼロから教えるDocker講座」 
https://www.udemy.com/course/aidocker/
→初心者がDockerを学ぶならこれ!!!めちゃくちゃわかりやすい!!!linuxの基礎からDockerでの環境構築を網羅しています。
今回の環境構築もこの教材をベースにしています(もちろんrailsのバージョンとかdbなど自分なりに変えているので同じではないです)
もしDocker全然わかないという人はこの講座をぜひ買ってみてください。

「いまさらだけどDockerに入門したので分かりやすくまとめてみた」 
https://qiita.com/gold-kou/items/44860fbda1a34a001fc1
→すごく細かく網羅しているのでとてもわからなくなったらよく見ています。リファラル的によくみさせていただいてます。

「Docker/Kubernetes 実践コンテナ開発入門 --著 山田 明憲」 
→実践的な書籍、あまりにも初学者だと少しむずかしいかも。 → 基礎覚え得たあとに見返したら非常に良かったです!

「たった1日で基本が身に付く! Docker/Kubernetes超入門 --著」
→初学者が書籍で学びたい場合はこれがとっかかりやすい。

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

多重配列から各名前とその中身の合計を出力する

【概要】

1.結論

2.どのようにコーディングするのか

3.ここから学んだこと

1.結論

eachメソッドの入れ子と添字を使用する!

2.どのようにコーディングするのか

vegetables_price = [["tomato", [200, 250, 220]], ["potato", [100, 120, 80]], ["cabbage", [120, 150]]] #---❶

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

❶:vegetables_priceを多重配列にして、トマト・じゃがいも・キャベツの中に3種類の金額を配列にしています。
❷:まず、1段階目の各配列を取り出したいのでvegetables_priceをeachメソッドを使用しvegetableで抜き出しています。(ex:["tomato", [200, 250, 220],["potato", [100, 120, 80]],["cabbage", [1200, 1500]])
❸:さらにeachメソッドを使用し、金額を抜き出しています。出力用に事前にsum = 0にしておき、各野菜に対して合計金額を計算するようにしています。[1]なのは、["tomato", [200, 250, 220]の状態だと配列なのでtomatoは[0]、[200, 250, 220]は[1]になるからです。(ex:tomato➡︎200+250+220,potato➡︎100+120+80,cabbage➡︎120+ 150)
❹:ここで各野菜の名前と各合計金額を出力しています。vegetable[0]で固定しているのも❸と同じ理屈です。

3.ここから学んだこと

二重ハッシュにして、目的の名前を入力した金額だけを合計することもできそうだと思いました。なので、キーバリューストアを使用してコーディングしてみます。

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

[Rails]本番環境EC2で"rails db:create"などの実行方法

投稿内容

ローカル環境で行うrails db:createrails db:migrateなどを本番環境で実行する方法について投稿します。

実行するディレクトリへ移動

まず本番環境で下記コマンドでcurrentディレクトリまで移動します。

ターミナル.
[ec2-user@******* ~]$ cd /var/www/アプリ名/current

コマンド実行

ターミナル.
//データベースを削除
[ec2-user@******* current]$ RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rake db:drop

//データベース作成
[ec2-user@******* current]$ rails db:create RAILS_ENV=production

//テーブル、カラムの作成
[ec2-user@******* current]$ rails db:migrate RAILS_ENV=production

//データベースのカラムに情報を送りたい場合
[ec2-user@******* current]$ rails db:seed RAILS_ENV=production

最後に

本番環境のコマンドはついつい忘れてしまいそうになるので、是非参考にしてもらえると幸いです!

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

「暗記」ダメ!!!! 絶対!!!!それって本当ですか?

暗記ってダメなの?

学習をしていく中で1度は耳にする

「暗記」はダメ

といった論調があると思います

最初に結論を言っておくと私はこの記事で
暗記を全て否定するのではなく、拾ってくる情報リソースの多くで頻出している「単語」や「概念」は暗記しましょうね。と伝えたいです。

私の背景

半年前の私は

・HTMLって何
・PHP何それ弱そう
・プログラミング=ホームページを作る事でしょ?

といったようにプログラミングに関してほとんど無知の状態でした。
今では半年間毎日1日8時間の学習を継続しているので、そんなふざけた状態ではないですが、当時は間違いなく全くの初学者でした。

私は初学者だった当時スクールに通うことはせずネットのPHP教材を購入し独学をはじめました。

少し話がそれますが、この半年でオンラインなどの交流会でスクールを卒業された方の話しを10人前後聞いてきたのですが、その方々の満足度は大変高い印象でした。ただし、接頭辞的な感じで、「挫折しない為には」いいですね。といった言い回しをされる方が多い印象でした笑

私も半年間独学をしてきた経験と周りの卒業された方の意見を合わせて考えると時間効率を求めるならスクールは大いにありだと思います。
スクールを通う段階としてはプロゲートで5つくらいの言語を一通りやってみてからでいいのかな〜といった印象です。
お金に余裕があって少しでも一人でやる自信がないと思っている方は即決でスクールを選んだ方が効率は絶対にいいです(こんな事言ってますがスクールの回し者ではありません笑。なんならエンジニアyoutuerみたいに一回くらい企業案件やってみたいです。この動画はテックアカ、、、、?)

さて本題に戻ります。

私はスクールには通いませんでしたがネットで教材を購入しそれを独学しました。その教材の中には「暗記してください」などとは書かれて言いませんでしたが、重要なポイントとして取り上げられているTipsはたくさんありました。

要するに何が言いたいかと言うと

単語レベルの理解はもちろん、多くの人が共通して主立って「重要」と言っている事は四の五の言わず暗記した方が後の開発効率がスムーズになる。

と言うのが私の意見です。

そんなの当たり前だろ。。記事にすることじゃないよね。。と思われるかもしれませんが

初学者の方で
・「暗記」をひとまとめにして「悪」と捉えている
・「暗記」って本当にダメなのか疑心暗鬼になっている

方は少なくないと思い、この記事を書きました。

少なからず私が学習初期の段階では色んな情報に触れて
「暗記は効率悪いんだ」と一括りにしてしまっていました。

暗記しなくてもいい事

もちろん暗記する方が圧倒的に効率が悪くなることもあると思います。
そこで、大きく分けてみると3つに分類できるかなと思います。
※前提として忘れないで頂きたいのはこれは学習して半年程度且つ実務未経験者の意見です?また、PHPやLaravel以外の言語に関しては詳しくないのでご了承ください。笑

1. コードの書き方を暗記する
  私が考えるに暗記してはいけないと情報発信されている方の本質的部分はここだと思います。
  ここは私が細かく言うまでもないですが、効率が悪く、キリがないですよね。笑
  これをやってしまっている方がいたら今すぐにその習慣を辞めて、コードを書いて覚えましょう!
  無心で書き続けていればいつの間にか覚えます。必ずです。

2. 開発環境の構築の仕方
  これはキリがないとか効率が悪いとか言うより初学者のうちにここを重点的に覚えようとすると、挫折率が上がってしまうかと思います。
  サーバーやネットワークの知識が必要となってくるので初学者の段階は
 「そう言うものを使っているんだな」程度の理解でいいのではないでしょうか。

3. 関数を全て覚えようとする
  これもコードを暗記するに近いですが、仮に全て覚えても一生使わないものも出てくる可能性があり且つ効率が悪いからです。
  経験上、使用頻度の高い関数はコードを書いて入れば勝手に覚えます。つまり使用頻度が高い=重要度の高い関数と言う認識で良いかと。

暗記した方がいい事

さて、今度は逆に暗記した方がいい事について少しみていきましょう。
冒頭でも述べましたが、暗記をした方が良い内容は 拾ってくる情報リソースの多くで頻出している「単語」や「概念」の事でしたね。

では具体的にはどう言った単語や概念でしょうか。
私の経験から「早めに覚えておいてよかった」「ここは早めに深く覚えておけばよかった」点から解説します。
※ここからはさらに個人的主観が強くなりますでご了承ください
以下の内応は一応、PHPを対象としていますの
if文/繰り返し文/関数/引数/戻り値/変数
と言ったどのプログラミング言語でも共通する基礎の部分は除きます

ざっくりですが以下2点の理解をまずは「暗記」する事を私はお勧めします。

1.$GETと$POST

2.データベースは何をしているか

詳しくは次の段落で書いていきます。

$GETと$POST

 [なぜこれを覚えておいた方が良いのか]

 それはPHP言語でCRUDを実装する時に超頻出の概念だからです。
 (CRUDとは、システムに必要な4つの主要機能である「Create(生成)」「Read(読み取り)」「Update(更新)」「Delete(削除)」の頭文字を並べた用語です。)
 CRUDはWEBアプリ制作する上で基本的な考え方になります。

 PHPとよく比較されるプログラミング言語が、JavaScriptです。この両者はスクリプト言語でも、動作については全く異なります。
 JavaScriptはHTMLやCSS、画像と同じようにブラウザを表示しているPCにファイルをダウンロードしてから実行し動作するのに対し
 PHPはWebサーバ上にファイルを置いて、以下の順で実行されます。

 1.ブラウザを見ているユーザがクリックなど何かの操作をする
 2.その動作を受けたプログラムがサーバで動作する
 3.動作結果をレスポンスとして、インターネット経由で送り、ブラウザ上にHTMLを表示する
 
このようにPHPのような言語は、サーバサイドのプログラミング言語と呼ばれ、PHPはサーバー側で動くプログラミング言語でこの全体の処理の中でも、$_GETと$_POSTは超頻出です。つまりこの2つの処理が何をしているのかを「暗記」する事はPHPを早く上達する上では効率が良いと言えます。
 

 GETやPOSTについてもっと詳しく知りたいと言う方はこちらの書籍を読んでみるのもいいかと思います。
 HTTP通信についての理解が深まりクライアントとサーバーが裏でどんな事をやっているかのイメージがつくかと思います。
www.amazon.co.jp/dp/4774142042

データベースは何をしているか

データベースを扱うアプリケーションでは「CRUD」のアクションが基本となります。
つまり、データベースに対して行うアクションは、

・データベースにデータを「新規登録」する、
・そのデータを「読み出す」、
・そのデータを「変更し上書き」登録する、
・そのデータを「削除」する、

という、「基本この4つしか無い」ということです。

つまりこの流れのイメージを意識的に暗記する事でコードを書いている時に、今何やっているかの把握ができ結果として開発効率が上がると考えます。

[補足]
個人的にはSQL文法も暗記しておいた方が効率が良いのではと思っています。これも超頻出なので。笑

最後に

まとめとして、頻出する単語や概念、その周りの流れは覚えてしまった方が結果的に効率が上がると言う事です。

本記事とは少しずれますが、有名な方の発言を鵜呑みにするのではなく、その意見を一回自分の頭に持ち込んで、処理して自分なりの答えを導き出すと言う事も重要です。この「脳に対する意識的な習慣」ができていると、エラー時にもイライラしなくなると思います。(エラーでイライラしてしまう人は他責思考が強い人だと思う)
この事は自戒を含めて伝えたいなと思いました。

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

Railsモデルのメソッドの命名について

はじめに

Railsの命名について、Model, Controllerやテーブルに対する命名のことは数多く書かれているが、メソッド名、変数名、スコープ名についてはあまり書かれていないように見受けられる。

命名の良し悪しはコードの可読性に大きく影響を与えるが、一方で英語が苦手な僕たちは度々血迷ったネーミングをしてしまい、しばしば技術的負債を溜め込む。

そもそもrubyは英語っぽい書き味(あくまで「ぽい」)を意識した言語仕様になっているので、メソッド名、変数名、スコープ名についてもある程度このマナーに則るべきだと思う。そこで改めて、これら命名が従うべきガイドラインを考察してみた。

色々整理しながら書くので小分けにしたく、今回は一旦モデルのメソッド、スコープ、(少しだけ)変数の命名だけ考えたが、後日コントローラーについても考えるつもりでいる。

TL;DR (モデル編)

※ いろいろ考えているうちに頭がこんがらがってきた。後で修正するかもしれません。

命名対象 形式
スコープ 「形容詞(句)」, 「前置詞_名詞」, 「動詞ing」, 「動詞の過去分詞形」
作用を持つメソッド 「他動詞」 → 他動詞の目的語がレシーバーobjectになるように!
判定系メソッド 「形容詞(句)?」, 「前置詞_名詞?」, 「動詞ing?」, 「動詞の過去分詞形?」
作用を持たないメソッド 「名詞(句)」 → プロパティとして使えるように
オブジェクトそのものを変換するメソッド 「to_名詞」
変数 「名詞(句)」
変数(例外) 「動詞の過去分詞形_前置詞(_名詞)」 → 避けられるなら避ける

使用する単語の品詞に留意する。そして、読んで意味がわかるのが一番大切。

詳細

スコープ名

形容詞または形容詞句であるべき。
スコープ名がモデル名を後置修飾する。
「どんな〇〇」の「どんな」の部分をスコープ名にする。
〇〇にはモデル名が入る。

形容詞句で後置修飾するようにスコープを命名しておくと、スコープチェーンしたときにも英文法的に破綻しない。
Book.in_sale.published_by("O'Reilly") ← Book (which is) in sale published by O'Reilly

典型例

モデル名.動詞+ing または モデル名.動詞の過去分詞形

Shop.selling_books # 本を売っている店
Task.not_running # 否定形
Book.published # 過去分詞形
Book.published_by("O'Reilly") # 引数も取るスコープ

Book (which is) published by O'Reilly

(which is) が省略された名詞句に見えるのが良い命名です。

モデル名.前置詞+名詞

Shop.without_blue_roof
Devil.in_the_prada
Man.with_the_machine_gun

モデル名.形容詞

Mission.impossible
Daemon.alive
Picture.not_displayable
Tower.taller_than(100)
Session.expired # 補足) expired は形容詞ともみなせるし、自動詞の過去分詞形ともみなせる(大きな差はない)

だめパターン

時々 :filtered_xxxx というスタイルのスコープ名を見かけることがあるけど、そもそもスコープの機能が絞り込みなのでこういう命名は冗長。

:can_xxxx も推奨しない。いい命名かどうかの判定は, 「Model (which is) scope名」というふうに省略していた which is を復活させて文法的に崩壊しないかどうか。

メソッド名

作用(変数等の変更)を伴うメソッドか、作用のないメソッドかで考え方が大きく異なってくる。

作用を伴うメソッド

「主語(呼び出し元オブジェクト)」が「目的語(レシーバー)」を「どうするか」。
「どうするか」の部分をメソッド名とする。
どうにかした結果として内部の変数状態を変えたり、外部のオブジェクトに作用したり、外部のサービスにアクセスしたりする。

cow.grow # <=  I grow up the cow. その結果 cow.age はインクリメントされる、などの作用が起こる。
file.delete # <= I delete the file. その結果、(プログラムにとっては)外部のファイルシステム上でファイルが消える作用を起こす。
job.perform # <= I perform the job.

時々、レシーバー(オブジェクト)が主語とされるようなメソッドを見かけるが、良くない。
一瞬良さそうに見えるかもしれないが、長い目で見るとオブジェクト指向じゃなくなってしまい、崩壊し始める。
※ オブジェクトに対してメソッドというインターフェースを通じて作用を加える。この連鎖で作るのがオブジェクト指向開発。

# 悪い例
manager.evaluate(member) # <= 語順そのまま A manager evaluates his member.

作用を伴わないメソッド

判定系メソッド(Predicate methods)

truefalseを返す判定系(作用しない)メソッドは、通常の(作用を起こす)メソッドと明確に命名のアプローチが違う。
この場合に限り、レシーバーは主語扱いする。

例えば、Shopopen状態かどうかを判定するStatementは

shop.open? # <= Is the shop open?

他の言語だと、しばしば shop.is_open というbe動詞付きのノリで判定系メソッドを作るが、
Ruby / Railsの通例ではbe動詞は省略して、末尾に?をつける。?が使えるという言語仕様特有の文化だと思うし、そもそも疑問形だと「S+V+C」は「V+S+C?」になるので、主語と補語の間には is は入らない。

判定系メソッドの場合に限り、メソッド名は「補語句的」である必要がある。(S=object, V=省略, C=メソッド名)
→ スコープの命名と似ていて、「形容詞?」か「前置詞+名詞?」か「動詞+ing?」か「動詞の過去分詞形?」が基本となる。

can_xxx, can_be_yyy は有りか無しか? できるだけ避けたほうが良いとは思う。後で考える。

判定系の否定メソッド

shop.not_open? というメソッドは極力作らない。 !shop.open? と書けば済む。
判定条件が複雑になると、そのメソッドの可読性も落ちてしまうので、そもそも否定の意味を内包しないように設計するのが吉だと思う。

作用は伴わないが判定しないメソッド

true / false を返すわけではないメソッドは, 外部からプロパティとして利用できるようなものはプロパティとして定義するのが良さそう。
このときの命名は後述の「変数名」と同じスタイル。

もしくは、オブジェクトそのものを別の形式に変換するなら to_xxx という命名にすべき。convert_to_xxx とはしない。

例えば

class Sylinder
  attr_accessor :bore, :stroke

  def initializer(bore, stroke)
    @bore = bore
    @stroke = stroke
  end

  def displacement # <= オブジェクトの内部状態に作用しないので :calculate_displacement のようなメソッド名にはしない
    (@bore/2) * (@bore/2) * Math::PI * @stroke
  end
end

変数名

基本的には名詞句を変数名としたい。
変数はオブジェクト(またはクラス)が所有する属性。属性名は名詞。名詞の前に形容詞とか所有格を付けて意味を絞り込んでも良い。

user.email
user.first_name

例外的に、そのオブジェクトに対するアクション(作用)に関する補助的な情報を保持する変数は、動詞の過去分詞形+前置詞(+名詞)という形式の変数名となることがある。
こういう変数名を使わずに表現できるのであれば、避けたほうが後で読みやすい気がする。

article.created_at
article.published_by
article.referenced_in_indices

考察

Ruby言語仕様

automator.call bob if not bob.awake?

みたいにメソッドの引数を括弧無しで呼び出せるところや、not, andキーワードが定義されているあたりからも、
もともと記号的記述を少なく押さえて自然言語っぽく書けるようになっている。
呼び出す際に自然言語っぽい表現になるように、スコープやメソッド、変数の命名も工夫してあげるとコードリーディングのストレスが軽くなる。

英文法の基本

英文法は基本的に下記の5文型に類型されることが知られている。

  1. S+V
  2. S+V+C
  3. S+V+O
  4. S+V+O+O
  5. S+V+O+C

文型の構成要素と、それぞれが対応する Rails プログラミングにおける概念は下記になると思う。

S: 主語(主部) → 呼び出し元(サブジェクト)
V: 動詞 → メソッド
O: 目的語(名詞) → レシーバー(メソッドを生やしているオブジェクト) と, メソッド引数(第4文型の場合)
C: 補語(形容詞/副詞/形容詞句/副詞句) → 代入値

S+V+Oの"O"はオブジェクト指向(Object-oriented)の"O"です。

オブジェクト指向的言語がカバーすべき表現力

基本的にメソッド呼び出しは第3文型のセンテンスを表現していると考えて良さそう。それ以外は、代入やプロパティ変更として実装するか、第3文型に変換して実装することになるか。

第1文型: S+V

あんまりない。なぜなら、オブジェクト指向は呼び出し元がレシーバーに対して作用することで処理を実装していくパラダイムだから。
シンプルな file.delete() とかは、実は第3文型だと思う。

file.delete # <= I deletes the file.

第2文型: S+V+C

O (Object)が登場しないので、これもメソッドの形としてはあまりないケース。あるとすれば呼び出し元オブジェクトが自分のプロパティに何かを代入するとか、そういう実装になると思う。

この文型をとるV(動詞)のバリエーションは下記のとおり。

状態型: be, look, seem, appear, keep, remain, lie
変化型: become, get, turn, grow, make, come, go, fall
感覚型: smell, taste, feel, sound
出典: https://www.eng-builder.jp/learning/sentence-pattern2/

つまり、これらの動詞は変化を伴うか伴わないかなどの違いはあれどどれも結果的にSがCという状態 (S = C) であることを表現しているわけである。
この表現をプログラミングで記述するにあたり、大概の場合はオブジェクトの状態を表すプロパティに対して値を代入することで実現できそう。

alice.health = :fine # <= Alice becomes fine.

第3文型: S+V+O

これはわかりやすい。一番多そう。

file.delete # <= I delete the file.
window.move_to(100, 300) # <= I move the window to (100, 300).

与えられる引数は副詞句として文全体を修飾する役割に使われることが多い気がする。
要するに、引数はオブジェクトに対する作用を調整するパラメータ。
パラメーターは呼び出された側のメソッド内ではconstであるほうがいいような気もするけど、そこまでまだ自分の中で考えられてません。

第4文型: S+V+O1+O2

この文型をとるV(動詞)のバリエーションは下記のとおり。

give, show, offer, hand, pass, send, teach, tell, pay, lend, do

傾向として, SがO1にO2を与えるような意味になっていると分かる。
オブジェクト指向的プログラミングで記述する場合、プロパティへの代入/追加などで表現できることが多い気がする。

alice.belongings.push book # <= I give Alice a book
alice.give book # <= I give Alice a book

第5文型: S+V+O+C

この文型をとるV(動詞)のバリエーションは下記のとおり。

make, get, keep, leave, think, believe, find, call, name

どれも O=Cであるという状態を表現する動詞ばかりであることが分かる。
SがOに働きかけるか否かの違いはあるが、結果的に 「Oの何らかの状態がC」になったり、 「Oの何らかの状態がC」であることを認識するという意味の動詞。
これは、オブジェクト指向的に実装しようとするとCとOだけ使ってもっと単純なSVC表現に書き換えることができそう。

Room.tidiness = :clean # <= I make the room clean.
cat.name = "Tom" # <= I name the cat "Tom."

まとめ

ややこしくてもうわからん。なんかまとまりの悪い文章になってしまった。

参考にしたページ

オブジェクトは目的語
How To Name Variables And Methods In Ruby

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

アソシエーション(1対1)!!

アソシエーション?

簡単に説明するとモデルを利用したテーブル同士の関連付けのことですね。
テーブル同士で関連付けておき、一方のモデルからもう一方のモデルに
アクセスできるようにするためということですね。

関連付けを行う理由

Railsでは、「関連付け(アソシエーション: association)」とは2つの
Active Recordモデル同士の繋がりのことですね。

2つモデルの間には関連付けを行なう必要がありますが、
その理由を知っていますか?

それはですね、関連付けを行う事でコードの共通操作をより
シンプルで簡単にできるからなんです。

アソシエーション(1対1)定義の際に使用するメソッド

has_oneメソッド

アソシエーションが1対1の時に使われます。
注意点としては、親モデル側に「has_one」の記述を、
子モデル側に「belongs_to」を書きます。

「belongs_to」メソッドについては下記のURLに説明がありますので、
そちらを参考にして下さい。今回は説明を省きます。

belongs_toメソッド参考記事

まとめると下記のようになりますね。

has_one.png

今回、説明する上で下記のアソシエーション(関係)で記述例を
載せていきます。

                userとaddress         1対1

アソシエーションの定義記述方法

先にUserモデルの記述例を載せていきます。

/models/user.rb
class User < ApplicationRecord

   has_one :address
end

書き方としましては、has_one :モデル名(単数形)
今回は「1対1」のためモデル名は単数形になります。

Userモデルの記述は以上です。

次は、addressモデルの記述にいきましょう。

/models/address.rb
class Address < ApplicationRecord

  belongs_to :user

end

書き方としましては、 belong_to :モデル名(単数形) になります。

これで、Userモデルとaddressモデル間のアソシエーションができましたね。

まとめ

「1対1」のアソシエーションには、has_oneを用いましょう!!
「1対1」を記述する際は、「1対多」・「多対多」などのように
モデル名が複数形ではなく、単数形で記述するのであまり気を付けなくても
大丈夫ですね笑

これまでで、「1対1」・「1対多」の説明をさせて頂いたので残りは
「多対多」になりますね。

これで「1対1」のアソシエーションの定義の仕方などわかって頂けたら嬉しいです。
ご覧頂きありがとうございました。

以上。

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

ユーザー画像編集時にプレビュー画像を表示する

はじめに

ユーザーの画像変更時に、ファイルは上げたもののはっきり変更されてるのかどうかが分かりづらかったため、jQueryを使ってうまいことプレビュー表示させるようにした(フロント苦手、、、)。

ruby 2.6.3
rails 6.0.3
アップローダー ActiveStorage
jQuary

概要

やりたい挙動

ファイルアップロード前:既にユーザーに紐づいている画像をプレビュー表示
ファイルアップロード後:既にユーザーに紐づいている画像の表示を非表示にして、
            新たな画像ファイルをプレビュー表示

実装ファイル一覧

users
①edit.html.slim
②_form.html.slim(form部分だけ切り出して使用。全てeditに書いても問題ありません。)
③javascripts/app/users/edit.js

実装

edit.html.slim
- content_for :title
  = I18n.t 'helpers.title.edit', model: t_model(:user), name: @user.name

== render 'form', model: @user
_form.html.slim
= render 'shared/validation_messages', model: model
= simple_form_for model, html: { class: 'form-horizontal js-editing' } do |f|

  .page-content.container.profile_edit
    .card
      .card-header
        h5.card-title
          | 基本情報
      .card-body.p-3.ibox-content
        .no-padding.no-margins
          .circle-avatar
            img src="#" id="avatar_img_prev" class="d-none"
            - if model.image.attached?
              = image_tag(model.image, class: 'avatar_present_img')
            - else
              = image_tag("defaurt-user.png", class: "avatar_present_img")
          |
          br
          = f.file_field :image, id: "post_img"

          = f.input :name, placeholder: '山田 花子'
      / 以下略
javascripts/app/users/edit.js
/* global $ */

$(function() {
  function readURL(input) {
    if (input.files && input.files[0]) {
      var reader = new FileReader();

      reader.onload = function (e) {
        $('#avatar_img_prev').attr('src', e.target.result);
      };
      reader.readAsDataURL(input.files[0]);
    }
  }

  $("#post_img").change(function(){
    $('#avatar_img_prev').removeClass('d-none');
    $('.avatar_present_img').remove();
    readURL(this);
  });
}());

やっててつまづいたとこ

・slim記法でのimgの書き方はimg src: "~~", id: "~~"ではなく、img src="~~" id="~~"と書く(滅多に書かないから分からなかった、、、)。

img src="#" id="avatar_img_prev" class="hidden"のclassがうまく反映されなかったため、class="d-none"に変更 → 効いた

・jQuaryをform.slim上に書いてうまく反映されなかったため、ちゃんとjsファイルに書き直した。

参考

【Rails5】アップロードした画像を即時プレビューする

Webアプリ tsunagaru をリリースしました [4] ActiveStorage から CarrierWave への移行

Bootstrap4 displayクラスの使い方を徹底解説

html.erbファイルでjs直書きでその中にrubyのコードを埋め込んでいる状態のものをslimに置き換えるときの書き方

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

[rails]タグのランキング機能

今日ポートフォリオにタグ付け機能を追加し、ランキングを作ったので
アウトプットします。

タグ付け機能はformオブジェクトを使いました。
まずtweetコントローラーにこのように記述します。

tweets_controller.rb
def rank
   @tag_ranks = TweetTag.find( TweetTagRelation.group(:tweet_tag_id).order('count(tweet_tag_id)desc').limit(4).pluck(:tweet_tag_id))
 end

pluckって何・・・・って思ったので調べてみると
公式では
pluckは、1つのモデルで使用されているテーブルからカラム (1つでも複数でも可) を取得するクエリを送信するのに使用できます。引数としてカラム名のリストを与えると、指定したカラムの値の配列を、対応するデータ型で返します。

だそうです。

pluckの引数にtweet_tag_id(これは中間テーブルのタグのカラム)をとることで中身が取得できます。

次にviewです tweetsの配下にファイルを作ってこのように記述してください

rank.html.erb
<div class="rank-tag-all">
        <% @tag_ranks.each.with_index(1) do |tag,i| %>
          <div class="rank-tag-num">第<%= i %>位 </div> 
          <div class="rank-tag"><%= tag.name%></div>
        <%end%>
</div>

with_index(1)を書いて第二引数をとることで順位が表示されます。

雑談
かなり眠いので簡素的にかきました。。。
もっと早い時間にqiitaを書く予定を立てようと思います...

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

Railsで簡単にプルダウンを作る方法

簡単にプルダウンを作る方法

使用するディレクトリ(私の場合)⚠️命名はご自身で

  • ビューファイル(new.html.erb)
  • コントローラー(mentors.controller.rb)
  • モデル(sukill_id.rb)

目指す完成形

95c0f8244478d3536e6dd971b06a4a0f.png

まずビューファイルから(どこからでもいい)

1.こういうパターンや

new.html.erb
<select>
          <option>チャットするユーザーを選択してください</option>
          <option>ユーザー1</option>
          <option>ユーザー2</option>
        </select>

2.こういうパターンもある

new.html.erb
  <div class="mentors-detail">
   <div class="form">
     <div class="weight-bold-text">
       スキル
       <span class="indispensable">必須</span>
     </div>
     <%= f.collection_select(:skill_id, Skill.all, :id, :name, {}, {class:"select-box", id:"mentor-skill"}) %>
   </div>

今回は2のf.collection_selectを使用してプルダウンを作成します!

6行目で「:skill_id, Skill.all」となっていますがこれは選択したいプルダウンの内容は「skill_idモデル」から選ぶわけなので次はモデルの中身を書いていきます!

モデル作成

skill_id.rb
class Skill < ActiveHash::Base
  self.data = [
    { id: 1, name: '--' },
    { id: 2, name: 'HTMLCSS' },
    { id: 3, name: 'Ruby' },
    { id: 4, name: 'Javascript' },
    { id: 5, name: 'PHP' },
    { id: 6, name: 'Python' },
    { id: 7, name: 'SQL' },
    { id: 8, name: 'GO' },
  ]
end

あとはコントローラーにnewアクションやらcreateアクションを記述してルーティングやパスをrails routesで確認しながら記述すれば出来上がり!

95c0f8244478d3536e6dd971b06a4a0f.png

まとめ

CSSの部分は省きましたが多分こんな感じ(ここはご自身でやってみてください)

new.css
.mentors-detail {
  display: flex;
  justify-content: space-between;
  padding: 2vh 0;
}

.mentors-detail>.form {
  width: 300px;
  padding: 2vh 0;
}

.select-box {
  margin: 2vh 0;
}

現場からは以上です!

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