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

ESC - EC2 環境でnginx + rails

こんな感じで行けそうだったので、全体像をメモ
ecs-nginx-rails.jpg

  • nginxとrailsを一つのサービスで定義、ネットワークのリンクでつなげる。
  • railsのコンテナはポート公開しない
  • ローリングデプロイや同時起動のため、nginxは動的ポートにしておく
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ActionCableを使う際のnginxの設定でハマった

はじめに

Ruby on Railsで作ったアプリにリアルタイムチャットの機能を付けたいと思って、ActionCableを使ったときの話です。

ローカル環境ではうまく動いたのですが、本番環境ではリアルタイムチャットの部分が動きませんでした。

原因を調べたところ、ActionCableで使うwebsocketという通信をするために、それに対応するようなnginxの設定が必要なようでした。

 (為参考)元々のnginxの設定

元々のnginxの設定です。
修正前と修正後の差分がわかりやすいようにこちらも載せます。
ActionCableを実装する前はこれで問題なく動いていました。

nginx.conf
 # https://github.com/puma/puma/blob/master/docs/nginx.md
upstream app {
  server unix:///app/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name ***.***.***.***; # アプリのIPアドレス

  keepalive_timeout 5;

  # static files
  root /app/public;

  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;

    # static files
    if (-f $request_filename) {
      break;
    }
    if (-f $request_filename.html) {
      rewrite (.*) $1/index.html break;
    }
    if (-f $request_filename.html) {
      rewrite (.*) $1.html break;
    }

    if (!-f $request_filename) {
      proxy_pass http://app;
      break;
    }
  }

  location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ {
    expires max;
    break;
  }
}

ActionCableに対応したnginxの設定

websocketの通信を扱えるようにするために
location /cable以下を追加しました。

nginx.conf
# https://github.com/puma/puma/blob/master/docs/nginx.md
upstream app {
  server unix:///app/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name ***.***.***.***; # アプリのIPアドレス
  keepalive_timeout 5;

  # static files
  root /app/public;

  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;

    # static files
    if (-f $request_filename) {
      break;
    }
    if (-f $request_filename.html) {
      rewrite (.*) $1/index.html break;
    }
    if (-f $request_filename.html) {
      rewrite (.*) $1.html break;
    }

    if (!-f $request_filename) {
      proxy_pass http://app;
      break;
    }
  }

  #以下を追加

  location /cable {
    proxy_http_version 1.1;
    proxy_set_header Upgrade websocket;
    proxy_set_header Connection Upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://app/cable;
  }

  #追加部分ここまで

  location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ {
    expires max;
    break;
  }
}

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

【Bootstrap】ボタンの中に無駄な四角が入ってしまう際の対処法

無駄な四角が入ってしまうコード

  <button type="button" class="btn btn-success"><div class="actions">
    <%= f.submit  %>
  </div></button>

下記のように変になってます。

image.png

解決法

submitボタンの中にbootstrapをかく。

<div class="actions">
    <%= f.submit('投稿する',class: 'btn btn-primary btn-lg my-1 mb-5 px-5')  %>
  </div>

image.png

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

100日後に1人前になる新人エンジニア(0日目)

100日後に1人前になる新人エンジニア(自己紹介)

営業社員から転職をしてエンジニアになりました。
Javaでポートフォリオを作り、今後はRailsで開発を行っていく予定です。

筋トレが好きです。
社会人の2年目です。
餃子も好きです。

目的

  • 技術的な蓄積
  • アドバイスをいただく
  • たくさんLGTMが欲しい

以上が主な目的です。
特に3つ目は私のエンジニアとしてのモチベーションに関わります笑

100日で1人前

なれるかはわからないです。
ただ期限を持ってやることが大切だと思うので
ワニさんを見習って100日にしました。ちなみに今日はまだ0日目

1日1日を大切にして進んでいきたいと思っています!
本格的な更新は明日からです。

本日はまだ導入です。

お願いします

多くの人に見てもらうこと、いろんな意見をいただくことが大切だと感じています。

肯定的な意見でも、否定的な意見でもすべて僕の財産になっていくと思うので、
何か思ったことがありましたらコメントをいただけると幸いです。

それではまた明日

1人前のエンジニアになるまであと100日

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

【RailsAPI + Vue.js】Pagyを用いたページネーションの実装

はじめに

RailsAPIとVuetifyでページネーションを作りました。
gemをどれにしようか調べてみたところ、Pagyがやたらとシンプル!軽い!ということらしいので、Pagyを使いました。

環境、使用技術

  • Rails 5.2.4.2
  • Pagy 3.8.1
  • Vue.js 4.3.1
  • Vuetify 2.2.21
  • axios 0.19.2

Vuetifyやaxiosは他のものでも置き換え可能かなと思います。

Rails側

Pagyの初期設定

How To | Pagyに書いてある通りです。

Gemfile
gem 'pagy', '~> 3.5'

毎度おなじみ$ bundle installを実行し、config/initializers/pagy.rbに設定ファイルを作成します。
テンプレートをコピペして、必要なところだけコメントアウトを外します。

config/initializers/pagy.rb
Pagy::VARS[:items] = 3  # 1ページに3件取得する

コントローラ

app/controllers/api/v1/tweets_controller.rb
class Api::V1::UsersController < Api::V1::BaseController
+ include Pagy::Backend

  def index
-   users = User.all
+   pagy, users = pagy(User.all)
    render json: users
  end
end

APIモードでない場合はこれだけで完成です。
PostmanでAPIを叩いてレスポンスを確認してみます。
スクリーンショット 2020-05-19 20.34.28.png
このように、userのデータが3件ずつ取得できていました(シリアライザーを使っているので、カラム名がキャメルケースになっていますが、デフォルトではスネークケースのはずです)。

しかし、これだけでは現在のページや総ページ数がわかりません。フロント側のページネーションコンポーネントではそれらのデータが必要なので、追加で記述していきます。
image.png

ヘッダーにページの情報を入れる

app/controllers/api/v1/tweets_controller.rb
+ require 'pagy/extras/headers'

class Api::V1::UsersController < Api::V1::BaseController
  include Pagy::Backend

  def index
    pagy, users = pagy(User.all)
+   pagy_headers_merge(pagy)
    render json: users
  end
end

引用:Headers | Pagy

この記述により、レスポンスヘッダーに以下の情報が格納されます。
スクリーンショット 2020-05-19 21.00.58.png

Left align Right align
Link 最初・最後のページ、前・次のページのリンク
Current-Page 現在のページ番号
Page-Items 1ページのuserの数
Total-Pages 全てのページ数
Total-Count 全てのuserの数

"Link"の中身(実際は一行)↓

<http://127.0.0.1:3000/api/v1/users?page=1>; rel="first",
<http://127.0.0.1:3000/api/v1/users?page=1>; rel="prev",
<http://127.0.0.1:3000/api/v1/users?page=3>; rel="next",
<http://127.0.0.1:3000/api/v1/users?page=3>; rel="last"

これでRails側の処理は終わりです。
共通化する場合は、after_actionを使う方法もあります(see 公式)。

Vue側

Vue-routerは使っていません。

テンプレート部分

Pagination component — Vuetify.jsを少しカスタマイズします。

<template>
  <div class="text-center">
    <v-pagination
      v-model="currentPage"
      :length="page.totalPages"
    ></v-pagination>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        requestUrl: "/api/v1/users",
        page: {
          currentPage: 1,
          totalPages: 5,
        }
      }
    },
  }
</script>

image.png
これでひとまずページネーションを表示することはできましたが、まだ、ボタンを押してもpage.currentPageの値が変わるだけです。

ボタンを押したときの挙動

コンポーネントから@inputイベントを受け取り、changePageメソッドで処理を行います。

<template>
  <div class="text-center">
    <v-pagination
      v-model="currentPage"
      :length="page.totalPages"
+     @input="changePage"
    ></v-pagination>
  </div>
</template>
<script>
export default {
  data () {
    return {
+     requestUrl: "/api/v1/users",
      page: {
        currentPage: 1,
        totalPages: 5,
      }
    }
  },
+ methods: {
+   changePage(val) {
+     // 処理
+   }
+ }
}
</script>
methods: {
  async changePage(val) {
    // "/api/v1/users?page=2"などにGETリクエストを送る
    const response = await this.$axios.get(`${this.requestUrl}?page=${val}`)
    // 受け取ったusersデータを格納する
    const { users } = response.data
    this.users = users
  }
}

ページ読み込み時のデータ取得

mountedで最初の画面描画時の動きを記述します。

async mounted() {
  try {
    // "/api/v1/users"にGETリクエストを送る
    const response = await this.$axios.get(this.requestUrl)
    // それぞれのdataにレスポンスの値を代入する
    this.page.totalPages = Number(response.headers["total-pages"])
    const { users } = response.data
    this.users = users
  }
}

最終的なコード

<template>
  <!-- usersの表示部分。省略 -->
  <div class="text-center">
    <v-pagination
      v-model="page.currentPage"
      :length="page.totalPages"
      @input="changePage"
    />
  </div>
</template>

<script>
import goTo from "vuetify/es5/services/goto"  // しれっと追加している
export default {
  data() {
    return {
      requestUrl: "/api/v1/users",
      page: {
        currentPage: 1,
        totalPages: 1,
      },
      users: []
    }
  },
  async mounted() {
    try {
      const response = await this.$axios.get(this.requestUrl)
      this.page.totalPages = Number(response.headers["total-pages"])
      const { users } = response.data
      this.users = users
    }
  },
  methods: {
    async changePage(val) {
      goTo(0)  // ページ最上部までスクロール。Vuetifyのメソッド
      const res = await this.$axios.get(`${this.requestUrl}?page=${val}`)
      const { users } = res.data
      this.users = users
    }
  }
}
</script>

ちなみに

追加でヘッダーに渡したい情報がある場合は、以下のように書くことで追加できます。requestUrlを初期値のdataで設定するのが難しい場合は、このようにヘッダーに渡して受け取る方法もあります。

  def index
    pagy, users = pagy(User.all)
    pagy_headers_merge(pagy)
    response.headers.merge!({ 'Request-Url' => request.url.sub(/\?page=\d+$/, '') })
    render json: users
  end

スクリーンショット 2020-05-20 18.31.43.png

参考リンク

rails APIでページネーションを実装する
【vue.js】 Vuetifyで簡単ページネーション(Paginations)

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

bundle exec annotate を実行しても反応しない

状況

annotateのGemをインストールしてbundle exec annotateを実行しても何も表示されずエラーも帰ってこない。

実行後、modelsのファイルを開いてもスキーマの情報は追記されていない。

解決方法

1.annotateの設定ファイルを作成

rails g annotate:install

2.設定ファイルを修正

Railsアプリケーションフォルダの
lib/tasks/auto_annotate_models.rake を開く

auto_annotate_models.rake
      'show_indexes'              => 'true',

上の'true'部分を'false'に変更

auto_annotate_models.rake
    'show_indexes'              => 'false',

3.annotateを実行

bundle exec annotate

上記の方法でうまく行った場合はコマンド実行結果に以下が表示される

Annotated(数値): "modelsのファイル名列挙"

その後、modelsのファイルを開くとスキーマの情報がファイル先頭に追記されている

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

rails の link_to

railsのhelper(link_to・form_withなど)でget以外のメソッドが使えなくなるとき。
それは、jsの設定がうまくいってないことが多々あります。

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

Railsで多階層カテゴリー(ancestry)にカウンター(counter_culture)を実装する

肉 > 鶏肉 > むね肉のような多階層カテゴリーがあった時、
レシピにむね肉カテゴリーを登録したら、親カテゴリーである肉 > 鶏肉もカウントする機能を実装してみた。

ancestryを用いた多階層カテゴリーにカウンターをつけている記事が見つからず、自分で調べてみて実装したので解説してみる。

同じような人がいたらぜひ参考にしてください。

はじめに

環境

Ruby: 2.6.6
Rails: 6.0.2.2

前提

  • レシピを掲載するサイトを作成している。

  • レシピDBとカテゴリーDBがある。

  • レシピには複数のカテゴリーがついている。(中間テーブル)

  • カテゴリーは多階層カテゴリになっている。
    例: 肉 > 鶏肉 > むね肉
    ※ ancestryで実装(経路列挙モデル)

  • 1カテゴリーに何個レシピがあるかカウンターを実装している

使用しているgem

ancestry: 3.0.7
counter_culture: 2.5.1

counter_cultureの導入はこちらを参照。
関連レコード数の集計(カウンターキャッシュ) - Qiita

現状

Recipes Table

Table name: recipes

 id            :bigint       not null, primary key
 title         :string       not null
 description   :string       not null

RecipeとCategoryの中間テーブル
1つのレシピに対して、複数のカテゴリーが登録される関係になっている。

Table name: recipe_categories

 id          :bigint           not null, primary key
 recipe_id   :bigint           not null
 category_id :bigint           not null

Categories Table

Table name: categories

 id            :bigint           not null, primary key
 name          :string           not null
 ancestry      :string
 recipes_count :integer          default(0), not null

Model

class Recipe < ApplicationRecord
  has_many :recipe_categories, dependent: :destroy
end
class Category < ApplicationRecord
  has_ancestry
  has_many :recipe_categories, dependent: :destroy
end
class RecipeCategory < ApplicationRecord
  belongs_to :recipe
  belongs_to :category
  counter_culture :category, column_name: :recipes_count
end

現状の問題点

1:1の関係だと下記を中間テーブルに入れるだけでカウントされる。

counter_culture :category, column_name: :recipes_count

今回は親カテゴリーも同時にカウントされる機能をつけたい。

例えば
照り焼きチキンのレシピに、むね肉のカテゴリーがついている。

Recipe.find(1)
=> #<Recipe
 id: 1,
 title: "照り焼きチキン"
>

RecipeCategory.find(1)
=> #<RecipeCategory
 id: 1,
 recipe_id: 1,
 category_id: 20
>

Category.find(20)
=> #<Category
 id: 20,
 name: "むね肉",
 ancestry: "1/10",
 recipes_count: 1
>

むね肉カテゴリーは以下の関係になっている。

id:1 > id:10 > id:20
肉 > 鶏肉 > むね肉

1:1のカウンターだと関連付けした「むね肉」はカウントされるが、「肉」と「鶏肉」にカウントされない。
親カテゴリーである「肉」と「鶏肉」もカウントされるように実装をしてみた。

親子カテゴリのカウンター実装方法

結論からいうと、こう実装した。

class RecipeCategory < ApplicationRecord
  belongs_to :recipe
  belongs_to :category
  counter_culture :category, column_name: :recipes_count,
    foreign_key_values: proc { |category_id| Category.find(category_id).path_ids }
end

foreign_key_valuesは外部キーを上書きするオプション。

通常だと関連づいているcategory_id: 20が外部キーとなり、Categoryのid:20のカウントが増減する。
foreign_key_valuesに配列形式で数値を渡すと、渡した値を外部キーとして対象すべてのカウントを増減する。

親子カテゴリーのすべてをカウントしたいため、例だと[1, 10, 20]の値を渡す形で実装する。

実装の解説

公式のリファレンスと翻訳解説されている記事を参考にした。
GitHub - magnusvk/counter_culture: Turbo-charged counter caches for your Rails app.
Rails向け高機能カウンタキャッシュ gem 'counter_culture' README(翻訳)|TechRacho

foreign_key_values: proc { |category_id| Category.find(category_id).path_ids }

proc { |category_id| }
まずprocで引数を渡す、この時に渡されるのは通常の時の外部キー(今回だと20)

Category.find(category_id)
対象のレコードオブジェクトを取得する。

.path_ids
ancestryの機能でpath_idsを行うとオブジェクトの親子関係のIDをリストで取得することができる。

参照: 【翻訳】Gem Ancestry公式ドキュメント - Qiita

実行してみると=> [1, 10, 20]が返ってくることがわかる。

Category.find(20).path_ids
  Category Load (1.0ms)  SELECT "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT $2  [["id", 20], ["LIMIT", 1]]
=> [1, 10, 20]

[1, 10, 20]foreign_key_values:にわたすことで親子カテゴリーすべてをカウントできるようになった。

カウント再計算の機能実装

counter_cultureにはcounter_culture_fix_countsというカウントを再計算する機能が実装されている。
すでにあるデータをカウントしたり、カウントのズレを修正するために使うのだが、foreign_key_valuesオプションを使うとこの機能が使えない。

RecipeCategory.counter_culture_fix_counts
=> Fixing counter caches is not supported when using :foreign_key_values;
you may skip this relation with :skip_unsupported => true

調べたところ、foreign_key_valuesを使った場合、自分で再計算機能を実装するしかないようなので実装してみた。

結論から、こう実装した。
すべて計算し直しているため、件数が多いと時間がかかるかもしれない。
良い実装方法があったら教えてください。

class RecipeCategory < ApplicationRecord

  #...

  # Categoryのrecipes_countをすべて再計算する
  def self.fix_counts
    Category.update_all(recipes_count: 0)

    target_categories = pluck(:category_id)
    # categoriesの個数を計算する => { 1: 10, 10: 3, 20: 1 }
    count_categories = target_categories.group_by(&:itself).transform_values(&:size)
    count_categories.each do |category_id, count|
      count_up_categories = Category.find(category_id).path_ids
      Category.update_counters(count_up_categories, recipes_count: count)
    end
  end
end

実行している内容は以下のとおり。

  1. カウンターをすべて0にする
  2. 中間テーブルにあるcategory_idを配列で取得
  3. 個数を計算する => { 1: 10, 10: 3, 20: 1 }
  4. それぞれのカテゴリーの親子カテゴリーIDの配列を取得
  5. 親子カテゴリーすべてのカウントを個数分増やす

3の個数を計算する方法は、以下の記事を参考にした。
配列に同じ要素が何個あるかを数える - patorashのブログ

5のカウントを個数分増やす方法は、以下の記事を参考にした。
[Rails+MySQL] カウンターの実装 【なければ新規作成したいし、あれば適切にインクリメントしたい】 - Qiita
ActiveRecord::CounterCache::ClassMethods

これにて、RecipeCategory.fix_countsを実行するとカウントの再計算を行うようになった。

結論

RecipeCategoryを以下にすることで親子カテゴリーのカウンター機能を実装できた。

class RecipeCategory < ApplicationRecord
  belongs_to :recipe
  belongs_to :category
  counter_culture :category, column_name: :recipes_count,
                             foreign_key_values: proc { |category_id| Category.find(category_id).path_ids }

  # Categoryのrecipes_countをすべて再計算する
  def self.fix_counts
    Category.update_all(recipes_count: 0)

    target_categories = pluck(:category_id)
    # categoriesの個数を計算する => { 100: 3, 102: 2 }
    count_categories = target_categories.group_by(&:itself).transform_values(&:size)
    count_categories.each do |category_id, count|
      count_up_categories = Category.find(category_id).path_ids
      Category.update_counters(count_up_categories, recipes_count: count)
    end
  end
end

ぜひ、参考にしてみてください。
FBや改善点がありましたら、コメントで教えていただけると助かります。

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

CircleCI × Capistranoでec2にデプロイできなくて困った。

CircleCIでCI/CDパイプラインを構築していたところ、どうしてもcapistranoの実行プロセスでSSHエラーが出るので、憤慨。結果から言うと、セキュリティグループのポートがマイIPからしか解放されないように設定していた故のエラーだったのですが、変更がめんどくさそうなのでもっといい方法がないかと模索したところ、半日くらいこのデバックに費やしてしまいました(とほほ)。
ですが、デバッグの過程で各々のツールに関する知識が深まったので、結果オーライかなぁと思うわけです。
以下、備忘録。

ーーーーーーーーーーーーーーーーーーーーーーーーーーー

【原因のエラーメッセージ】

cap aborted!
SSHKit::Runner::ExecuteError: Exception while executing as ユーザー名@IPアドレス: 
Net::SSH::ConnectionTimeout


Caused by:
Net::SSH::ConnectionTimeout: Net::SSH::ConnectionTimeout


Caused by:
Errno::ETIMEDOUT: Connection timed out - connect(2) for IPアドレス

Tasks: TOP => rbenv:validate
(See full trace by running task with --trace)

ムムム、Capistranoの設定の時に散々遭遇した覚えがある。

実際にこの処理が行われているのは、CircleCIのサーバー上に立てられたdockerコンテナなので(合ってますよね?)、どうにかこの中に入って原因を突き止めたい。

該当するワークフローの画面から、Rerun Job with SSH を選択し、
Enable SSH に表示されるIPをコピーする。
スクリーンショット 2020-05-19 23.26.59.png

capistranoのデプロイ先であるEC2インスタンスから、github用の秘密鍵を用いて、circleciのコンテナにSSH接続します。

(ここら辺の動きがまだよくわからん。CiecleCIにはgithubとの連携の際に公開鍵が登録されてるってことですか?)

【EC2インスタンス】
$ ssh -i ~/.ssh/aws_git_rsa -p Enable SSHの値
The authenticity of host 'Enable SSHの値 (Enable SSHの値)' can't be established.
RSA key fingerprint is 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
RSA key fingerprint is 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
Are you sure you want to continue connecting (yes/no)? 

確認されるので、yesと入力。

circleci@~~~~~~~~~ :~$ 

無事入れました。

コンテナ内を回遊してわかったんですが、結局ローカルでやろうとしてることをこのコンテナ内で再現しようとしているだけなんですよね。つまり、EC2に接続できなかったのは、ローカルの~/.sshに置かれている秘密鍵がcircleci@ :~/.sshには存在しないからなのです。思ったより単純でした。

ローカルの~/.sshにあるEC2用の秘密鍵をコピーします。

.ssh % pbcopy < ~/.ssh/himitsu_key_rsa

スクリーンショット 2020-05-20 11.09.50.png

サークルCI->プロジェクトページ->右上設定タブ->project settings

スクリーンショット 2020-05-20 11.12.15.png

サイドバーにあるSSHkeysを選択。

スクリーンショット 2020-05-20 11.13.46.png

Add SSH Keyから、EC2用の秘密鍵を設定します。

スクリーンショット 2020-05-20 11.18.13.png

Hostnameを、EC2のElasticIP。Private Keyに、先ほどコピーした鍵の値をCtrl+Vで貼り付ける。値がinvalidだと怒られる。PEM形式でないと鍵の値を保存できないみたい。

参考:https://amasuda.xyz/post/2019-07-27-ssh-keygen-openssh-to-pem/

上記の記事などを参考に、鍵の形式を変更します。

スクリーンショット 2020-05-20 11.29.52.png

BEGIN OPENSSH PRIVATE KEY が BEGIN RSA PRIVATE KEY
に変わっていれば保存できるはずです。

スクリーンショット 2020-05-20 11.48.36.png

登録を終えたら、上記画像のFingerprintの値をコピーして、.circleci/config.ymlのプロセスに記載します。

      - add_ssh_keys:
          fingerprints:
            - "コピーしたFingerprint"

      - deploy:
          name: Capistrano deploy
          command: |
            if [ "${CIRCLE_BRANCH}" != "master" ]; then
              exit 0
            fi
            bundle exec cap production deploy

ここでひとまず、githubにプッシュ。実行されたプロセスを確認してみると、

スクリーンショット 2020-05-20 12.00.19.png

fingerprintをもとに鍵の値をインストールしてくれています。

一旦コンテナの中に入って、確認してみましょう。

circleci@~~~~~~~~~:~$ ls .ssh
config  id_rsa  id_rsa_11111111111111111111111111111111  known_hosts

【id_rsa + _fingerprintから:を抜いた値】と言う形式で保存されている鍵があります(ここでは例として111...を記載しています)。

このファイルを展開してみましょう。

circleci@~~~~~~~~~:~$ cat .ssh/id_rsa_11111111111111111111111111111111
-----BEGIN RSA PRIVATE KEY-----

   【さっき登録した秘密鍵の値】

なるほど、さっき登録した秘密鍵は、【id_rsa + _fingerprintから:を抜いた値】という名前でCircleCIコンテナの.ssh配下に保存されているわけですね。

ローカルでは、環境変数を用いてこの鍵へのパスを指定していました。

【config/deploy/production.rb】

server 'ElasticIP',
   user: "ユーザー名",
   roles: %w{web db app},
   ssh_options: {
       port: 22,
       keys: ["#{ENV.fetch('PRODUCTION_SSH_KEY')}"],
       forward_agent: true
   }

しかし、ローカルとCircleCIコンテナでは鍵の名前が異なりますし、環境変数は各々のサーバーで設定する必要がありますから、CircleCI側で改めて環境変数を指定し直さなくてはなりません。

先ほどと同じく、ProjectSettingsから
スクリーンショット 2020-05-20 11.09.50.png

サイドバーのEnvironment Variablesを選択。
スクリーンショット 2020-05-20 12.25.25.png

Add Variableから
スクリーンショット 2020-05-20 12.26.02.png

環境変数を指定します。Nameはdeproy/production.rbで使用している変数名と同じものを指定。
valueはCircleCIコンテナにある秘密鍵へのパスを指定します。

スクリーンショット 2020-05-20 12.27.23.png

ここまでくれば大丈夫なはず!再実行をしてみる!!!

cap aborted!
SSHKit::Runner::ExecuteError: Exception while executing as ユーザー名@IPアドレス: 
Net::SSH::ConnectionTimeout


Caused by:
Net::SSH::ConnectionTimeout: Net::SSH::ConnectionTimeout


Caused by:
Errno::ETIMEDOUT: Connection timed out - connect(2) for IPアドレス

Tasks: TOP => rbenv:validate
(See full trace by running task with --trace)

ムカつくな、試しにEC2のインバウンドルールを変更します。22番ポートを完全に解放。

スクリーンショット 2020-05-20 12.38.16.png

スクリーンショット 2020-05-20 12.39.21.png

まんまと成功しやがって、畜生、マジで。まあそうだよな。マイIPからのSSHじゃなきゃ開けないって設定したの自分なのに、馬鹿みたいじゃないか。

CircleCIでは毎回新しくコンテナを立ち上げている(?)のでIPアドレスが流動的です。つまり、プロセスを開始するたびに、そのコンテナからの接続を許可し、処理が終わったらそれを破棄しなければならない。

参考:https://qiita.com/rintaro-ishikawa/items/02e6a63dbc90ea67a991

この記事を参考に次の記事で問題を解決してみます。

あぁ、早くReactさわりたい。

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

devise ページネーション など 基礎

忘れがちな余談

コントローラーのアクション内に定義されたインスタンス変数はそのアクションのビューで使用することができる。

書くカラムに保存されている値のことをプロパティ値と言います。

ヘルパーメソッド

Railsでは予めviewでHTMLタグを出現させたり、テキストを加工するためのメソッドが用意されています。
これらをヘルパーメソッドと言います。
simple_format 改行で<br>を付与、文字列を<p>で囲ってくれるメソッド
form_tag    フォームを出現させるメソッド
link_to     aタグを出現させるメソッド
などがあります。

CSSファイルの読み込みはHTMLのheadタグ内のstylesheet_link_tagから読み込まれます。

require_treeは、引数として与えられたディレクトリ以下のCSSファイルをアルファベット順に全て読み込むという意味がある。.は引数であり、カレントディレクトリを表します。

application.css
require_tree .

MVC

ルーティング→コントローラー→モデル→ビュー の順に処理が行われるのがRailsです。
モデル・ビュー・コントローラーを使用して処理を行うシステムをそれぞれの頭文字をとってMVCと言います。

フォーム

フォームとはユーザーが情報を入力し、その情報をサーバーに送信するためのもの。
HTMLのコード内にform要素を作成し、その中にinput要素やtextarea要素を入れることで作成できます。
formタグでもフォームは作成できるが、セキュリティの観念上、Railsではヘルパーメソッドである、form_tag, form_for, form_withを使うのが推奨されている。

ユーザーがフォームに入力した値は、コントローラー内ではparamsという変数に入っています。
paramsはハッシュオブジェクトだと考えましょう。
ビューでフォームに入力された情報は、コントローラーにキーと一緒にパラメーターとして送られます。
このパラメーターはparamsというメソッドを使うことで取得することができます。
使用方法は

params[:キー名]

form内のinputやtextareaにあるname属性の値がキー名に当たります。

ストロングパラメーター

ストロングパラメーターとは、指定したキーを持つパラメーターのみを受け取るようにするもの。
不正な情報などを受け取らないようにするのでセキュリティを強化できます。
任意のストロングパラメーター名_paramsというメソッドを作成することで定義ができます。

xxxxx.rb
private
def sample_params    ###任意のストロングパラメーター名_params
  params.permit(:キー名, :キー名)
end

メソッドの中にはparams.permitと記述して受け取ることを許可するパラメーターのキー名を続けます。
permitメソッドは、ビューから送られてきた情報のハッシュであるparamsから後に続けたキーの名前がバリューと共に新たなハッシュとして生成します。

privateメソッド

class内でprivateと記述するとそれ以降に定義したメソッドはclassの外部から呼び出せなくなります。
外部から呼ばれたら困るメソッドを守れたり、private以下は読まなくて良くなるのでコードの可読性が上がります。

image_tag

Railsではロゴやバナーなど固定して表示したい画像はapp/assets/images以下に置くことが普通です。

sample.html.erb
<%= image_tag 'sample.png' %>

pry-rails

pry-railsとは、Gemの一つであり、Rails向けのデバッグツールです。
これを使うことによりバグの有無を確認したり処理を途中で止めてソースコードが正しいか確認できます。
特に使う機能がbinding.pryです。
これを使うことによって処理を途中で止めて、かつrailsコンソールと同じことができます。止めたいソースコードのところでbinding.pryを記述しましょう。変数の値を取得したり、binding.pryを複数個設置することによって処理が正しく行われているか確認することができます。
処理を再開させたい場合はターミナルでexitを入力します。

sample_controller.
def create
  Sample.create(sample_params)
binding.pry
end

private
def sample_params
  params.permit(:name, :image, :text)
end

投稿画面で投稿する。
(new.html.erb)

するとターミナルでコンソールが立ち上がる

ターミナル.
[1] pry(#<SamplesController>)> params
=> <ActionController::Parameters {"authenticity_token"=>"DnHBKql5oQq9lARgEnK8gQiFAhnTvIVa1XH8d13u7tuVjDUGWPM1eEjVbF44iFms57+VE7+vT3SYUePDy3lwWQ==", "name"=>"aa", "image"=>"https://kumamoto.photo/archives/_data/i/upload/2019/03/13/20190313172208-2113bf9d-la.jpg", "text"=>"dddddddd", "controller"=>"samples", "action"=>"create"} permitted: false>
[2] pry(#<SamplesController>)> params[:text]
=> "dddddddd"

コンソールでparamsと入力すると投稿画面で入力した値がキー名と共にパラメーターとして出力される。

exitと入力すると処理が再開する。

ルートパス

URLにパスを付けない、ホスト名だけのURLのことをルートパスと言います。
routes.rbでの書き方は、

routes.rb
root 'コントローラー名#アクション名'

orderメソッド

インスタンスを並び替えるメソッドです。
全てのレコードを取得してきた場合レコードを並び替えられます。

.order("カラム名 順序の指定”)

ASCは昇順 日付で言うと古い方から
DESCは降順 日付で言うと新しい方から

ページネーション

ページを分割させてくれるもの。1ページ当たりの表示件数を決められます。
gem kaminariを利用することで実装できます。

pageメソッド
このメソッドでページ数を指定できます。全体のページ数でなく、今何ページにいるかと言うことです。

perメソッド
1ページあたりの表示件数を決められます。

sample_controller.
@samples = Sample.order("id ASC").page(params[:page]).per(10)

pageメソッドの引数は、kaminari を導入した際に、モデルクラスにpageというキーが追加されておりそれを記述しています。そのキーの値はビューで指定したページの番号となります。

devise-ログイン機能-

ログイン機能を簡単に実装できるgemです。

Gemfileにdeviseを記述して
bundle install後
アプリのディレクトリで
rails g devise:installと叩くと
設定ファイルが作成されます。
config/initialize/devise.rbconfig/locales/devise.en.ymlです。

アカウント作成のためのモデルを作らなければいけないので
rails g devise モデルクラス名でモデルなどの関連ファイルを作成します。
モデルファイルや、マイグレーションファイルが作成されます。
それと同時に、routes.rb

devise_for :モデル名の複数形

が追記されます。
devise_forはログイン周りに必要なルーティングを一気に生成してくれるdeviseのヘルパーメソッドです。

current_user, user_signed_in?などのヘルパーメソッドも使えるようになっています。

rails g devise:viewsでデバイスのビューファイルを作成できます。

Prefix

ルーティングのパスが入った変数のことです。

unless文

if文の逆で、条件式が偽の時にする処理を記述します。

unless 条件式
 偽の時に実行する処理
end

条件分岐が1行の場合は、1行で記述できる。
puts "ログインしてください" unless user_signed_in?
###ログインしてなければ(つまり偽)"ログインしてください"が表示される

redirect_toメソッド

Railsでは基本的にアクションの処理が終わるとアクションと同名のビューに遷移しますが、redirect_toメソッドを使えば別のアクションを実行したり、ビューに遷移させたりできます。

redirect_to { action: :index }

キーにaction:をとり、値にアクションの名前のシンボル型をとります。
例えば、:indexなどです。{}は省略できます。

before_actionメソッド

コントローラー内のアクションが実行する前に実行したいメソッドを指定できます。シンボル型のメソッド名で指定できます。
オプションでexceptonlyがあり、アクションごとに制限をかけられます。

テーブルにカラムを追加

rails g  migration Addカラム名Toテーブル名 カラム名:型

configure_permitted_parametersメソッド

deviseでログイン機能を実装した際、パラメーターの受け取り方が普通とは違います。ストロングパラメーターは初期状態では、メールアドレスとパスワードのみ受け取る設定がされています。追加するにはconfigure_permitted_parametersメソッドを使います。

application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?

def configure_permitted_parameters
  devise_parameter_sanitizer.permit(:追加したいアクション名, keys: [:追加するキー])
end
application_controller.rb
class ApplicationController < ActionController::Base
#  protect_from_forgery with: :exception
 before_action :configure_permitted_parameters, if: :devise_controller?

 def configure_permitted_parameters
  devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
 end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで検索機能を追加する

7つの基本アクション以外でルーティングを定義する時には、collectionかmemberを利用します。
collectionはルーティングに:idがつかない、memberは:idがつく。という違いがあります。
今回は検索機能なので:idが不要なcollectionを使用します。

config/routes.rb
Rails.application.routes.draw do
  resources :tweets do
    collection do
      get 'search'
    end
  end
end

モデルファイルに対してwhereメソッドとLIKE句を使用して検索の処理を作ります。
LIKE句は、曖昧な文字列の検索をすることができ、whereメソッドと一緒に使います。

app/models/tweet.rb
class Tweet < ApplicationRecord
  validates :text, presence: true
  belongs_to :user
  has_many :comments

  def self.search(search)
    return Tweet.all unless search
    Tweet.where('text LIKE(?)', "%#{search}%")
  end
end

コントローラファイルに対して以下の記述を追加します。

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  def search
    @tweets = Tweet.search(params[:keyword])
  end
end

検索フォームを追加するページに検索フォームを実装します。

検索フォーム
<%= form_with(url: search_tweets_path, local: true, method: :get) do |form| %>
  <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "sample"%>
  <%= form.submit "検索", class: "sample"%>
<% end %>

app/views/tweetsに検索結果用のビューファイルsearch.html.erbを追加し、検索結果を表示させる。

app/views/tweets/search.html.erb
<% @tweets.each do |tweet| %>
   <%= tweet.content %>
<% end %>

複数のカラムから検索したい場合

modelファイルを下記のように編集します。

app/models//tweets
def self.search(search)
  return Tweet.all unless search
  Tweet.where("title LIKE(?) or content LIKE(?)", "%#{search}%", "%#{search}%")
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでいいね機能を非同期で実装。js.hamlを使用

なにこれ

Railsでツイッターのように1つツイートに対して、
1人のユーザーがいいねを1回できることができ、
いいねボタンを押すと非同期で情報が変わるように作成します。

注意事項

初学者のコメント機能を実装した時の備忘録として書いてます。
長々と解説してますが、まずはコピペして実践してみるのをおすすめします。
筆者が言語化して理解するためにこの記事を書いてます。
ご指摘がありましたら何なりとお申し付け下さい。

前提条件

userテーブル、tweetテーブルは作成済みとして進みます。

全体の流れ

Likeモデルとコントローラーを作成
アソシエーションを組む
ルーティングの設定
コントローラーを編集する
いいねの部分テンプレートを作る
create.js.hamlとdestroy.js.hamlファイルを作成して非同期通信が行えるようにする。
以上!

LikeモデルとLikeコントローラーを作成

ターミナル
rails g model Like tweet_id:integer user_id:integer
rails db:migrate
rails controller likes create destroy

ここから基本一部抜粋です

モデルとアソシエーションの設定

user.rb
 has_many :likes

1つのuserは複数のlikeを持つのでhas_manyです。

tweet.rb
 has_many :likes, dependent: :destroy

 #既にいいねしているか確認するメソッド
  def like_user(user_id)
   likes.find_by(user_id: user_id)
  end

1つのツイートは複数のいいねを持つのでhas_manyです。
ツイートが消えたらいいねも消えてほしいのでdependent: :destroyを追加します。

like_userメソッドで、current_userが既にいいねをしているか確認します。
このメソッドのおかげでいいね済みor未いいねを判別してビューを変えます。

like.rb
belongs_to :tweet
belongs_to :user

validates :tweet_id, uniqueness: { scope: :user_id }

1つのいいねは1つずつのツイートとユーザーしか持たないので、belongs_toです。

バリデーションででuniquenessを追加することで
1つツイートに1つのユーザーしかいいねができないよう制限をかけます。
ルーティングの設定

ルーティングの設定をします。

routes
  resources :tweets do
#resources commentsはツイートに2つネストしてるだけです。気にしないで下さい。
    resources :comments, only: [:create, :edit, :update, :destroy]
    resources :likes, only: [:create, :destroy]
  end

ツイートにネストされた状態にします。
コメント機能などと同様にツイートに紐付いていいねができるので。

コントローラの編集

ライクコントローラーの編集

likes_controller.rb
  before_action :set_tweet

  def create
    like = Like.create(user_id: current_user.id, tweet_id: @tweet.id)
    like.save
    # @likes = Like.where(tweet_id: @tweet.id)
    # @tweet.reload
  end

  def destroy
    like = Like.find_by(user_id: current_user.id, tweet_id: @tweet.id)
    like.destroy
    # @likes = Like.where(tweet_id: @tweet.id)
    # @tweet.reload
  end

  private
  def set_tweet
    # @tweet = Tweet.find_by(id: params[:tweet_id])
    @tweet = Tweet.find(params[:tweet_id])
  end

解説します。
set_tweetメソッドで、現在表示しているorボタンを押したツイートを探して取得します。
set_tweetメソッドを消す代わりに、以下のように記述してもokです。
意味は同じです。書き方が違うだけです。

like = Like.create(user_id: current_user.id, tweet_id: params[:tweet_id])

createアクションの解説です。

likes_controller.rb
  def create
    like = Like.create(user_id: current_user.id, tweet_id: @tweet.id)
    # @likes = Like.where(tweet_id: @tweet.id)
    # @tweet.reload
  end

Like.createで新しいライクを作成します。
user_idにはcurrent_user.idを代入。
tweet_idは@tweet.idで現在取得しているツイートを代入します。
like.saveなどは必要ないですが、コードの可読性を高めるために書いてます
(そのコードがどんな処理をしてるか第三者から見て分かりやすいように)

destroyアクションは、いいねを削除している以外はcreateアクションとやってることは同じなので省略

likes_controller.rb
  # @likes = Like.where(tweet_id: @tweet.id)
    # @tweet.reload

コメントで伏せ字にしてある2行です。
私が参考にしたサイトは、書くことを推奨していたのですが、
自分が実験すると削除しても問題なく動作し、どこにこの変数が使われているか
分からなかったため、伏せ字にしてあります。

ツイートコントローラーの中身は編集しませんが、ビューの説明用に一応載せます。
1度も@like = Like.newも作りません。直接createするので必要なかったです。

tweet_controller.rb
def index
    @tweets = Tweet.includes(:user).order('updated_at desc').page(params[:page]).per(5)
  end

 def show
    @tweet = Tweet.find(params[:id])
    @comment = Comment.new
    @comments = @tweet.comments.includes(:user).order('created_at asc')
  end

次はビューの編集をします。

views/tweets/index.html.haml
.main
  .tweets
    .tweets__list
      = render 'tweet'

renderでツイートの一覧を表示しています。

views/tweets/_tweet.html.haml
- @tweets.each do |tweet|
  .tweets__list--item
    %div{id: "tweet_like_#{tweet.id}"}
      = render 'likes/like', tweet: tweet

下から1行目と2行目が重要です。
tweet_idというidを指定することで、コメント投稿機能と同様に
どのツイートなのかを識別しています。
js.hamlでrenderする送信先を指定している、イメージです。

例)tweet_idが1なら、

になる。
いいねアクションが起こると、このidで判断して情報を更新している。イメージです
views/tweets/_tweet.html.haml
      = render 'likes/like', tweet: tweet

renderの後に tweet: tweetと記載してますが、これは必要な情報です。
原因は分かりませんが、この記述がないとrender先である/views/likes/_like.html.haml/
で、変数tweetが読み込んでくれません。

自分の解釈だと、_tweetsで@tweetsをeach文でtweetに変えて1ツイートずつ表示しているので、文字としては同じtweetなんですが、render先だとtweetという変数が定義されてないので、tweet: tweet と記載する。みたいな感じです。
render先でも同じ変数として自動的に使わせてくれないんでしょうか??
renderさん難しい。

ツイート詳細ページにもrenderでいいねページを追加します。

views/tweets/show.html.haml
  .like
    %div{id: "like_#{@tweet.id}"}
      = render 'likes/like', tweet: @tweet

流れは先程と同じなのですが、showの場合はrenderでtweet: @tweetと指定しています。
この理由は、tweets_controllerでインスタンス変数@tweetと定義しているので、
このままだと/views/likes/_like.html.hamlで変数@tweetは使用できないので、
tweetで使えるようにしてます。

次の/views/likes/_like.html.hamlが一番の難関です!!

views/likes/_like.html.haml
- if user_signed_in?
  - if tweet.like_user(current_user.id)
    = link_to tweet_like_path(tweet.likes, tweet_id: tweet.id), method: :delete, remote: true do
      %i.fa.fa-heart{"aria-hidden" => "true", style: "color: red;"}
        = tweet.likes.count
  - else
    = link_to tweet_likes_path(tweet.id), method: :post, remote: true do
      %i.fa.fa-heart{"aria-hidden" => "true", style: "color: #C0C0C0;"}
        = tweet.likes.count
- else
  %i.fa.fa-heart{"aria-hidden" => "true"}
    = tweet.likes.count
dden" => "true"}
    = item.likes.count

rails routesも確認します。

tweet_likes POST   /tweets/:tweet_id/likes(.:format)                                                       likes#create
tweet_like DELETE /tweets/:tweet_id/likes/:id(.:format)                                                    likes#destroy

ここは大事なので全部の行で解説します。上から順に行きます。

ユーザーがサインインしてるか判別
like_userメソッドでそのツイートに対して既にいいねしてるか判別します。
既にいいねしてる場合は、いいねを削除するリンクを表示させます。

views/likes/_like.html.haml
tweet_like_path(tweet.likes, tweet_id: tweet.id)

この書き方で、likes#destroyを呼び出すことができます。
ネストしたリンク先を指定するのが複雑すぎる。詳しい方にご教授願いたいです。
likesの場合は複数あるからlikesだけど、commentとかの場合だと1つしかないから単数形にしないとダメみたいな感覚です。commentが複数だったら日本語的にもおかしいもんね

自分が調べた限りですと、以下の書き方でも正解でした。

views/likes/_like.html.haml
tweet_id: tweet.id ,tweet.likes[0].id
#likes[0]ってどういうこと笑。これが一番よく分からなかった。
tweet.id ,tweet.likes
#これが分かりやすいと思った。
tweet, tweet.likes
#すごい短い
tweet.likes, tweet_id: tweet.id
#わかり易さ重視。ちなみに順番入れ替えるとsyntax errorエラー出ました。よく分からん。

%iタグはボタン表示です。
tweet.likes.countでいいね数の一覧を表示します。
そのツイートが所持?繋がっている?いいね数です。
tweet.like_user(current_user.id)でelseだったら、まだいいねをしてないので
新しくいいねができるリンクを表示させます。
rails routesを見てもらえたら分かると思うんですけど、
新規投稿は1回しかid指定が必要ないから簡単なんですよね。
その後は先程と同じです。

ユーザーがサインインしてない場合は、リンクを表示せずにいいね数だけをカウントします。

js.hamlファイルを作成していきます

views/likes/create.js.haml
$("#like_#{@tweet.id}").html("#{j(render partial: 'likes/like', locals: { tweet: @tweet })}");
views/likes/destroy.js.haml
$("#like_#{@tweet.id}").html("#{j(render partial: 'likes/like', locals: { tweet: @tweet })}");

はい、実はどちらも中身は完全に同じです。
/tweets/_tweetと/tweets/showで指定してたidの部分に
renderしてフォームを送って部分テンプレートを呼び出して更新させるイメージです。

locals: { tweet: @tweet }

これの意味は正直理解できてません。でもこれがないと、
それぞれcreateとdestroyアクション発火時にDB上にデータは保存される
(いいねしたのは追加されてる)けど、非同期通信でエラーが発生します。
500エラーが出て部分テンプレートが更新されません。以下がエラー文です。

POST http://localhost:3000/tweets/5/likes 500 (Internal Server Error)

自分なりの解釈ですが、出来る限り説明します。

結論だけ先に言うと、renderでインスタンス変数@tweetを変数tweet両方とも使えるようにした。だと思います。

いいねでcreateアクションが起きるとどこの画面が更新されるかの流れで考えます。
tweet/からいいねを押した場合だと、
いいねをした時にまずrenderする前である_tweetに戻ります。
_tweetだと、@tweetsをeachしてtweetで繰り返し処理してます。
再度renderする前まで読み込まれて、render先でも変数tweetが使えるようになって無限ループする。みたいなイメージです。
でも、このイメージだと、2つ問題があります。
create.js.hamlで$("#like
#{@tweet.id}”)という記述がありました。
この場合だと、_tweetの時のビューの記述は以下のようでした。

_tweet.html.haml
%div{id: "like_#{tweet.id}"}

はい、idの指定部分がおかしいです。
先程の_likeまでの流れだと、インスタンス変数@tweetを変数tweetにしてました。
create.js.hamlの記述だと、tweetのidを指定する変数が違うので読み込まないと思いました。でも結論で言ったとおり変数が2つ両方とも使えるっていう認識だったら上手くいく。

tweets/showの方は文字通りだったので、省略
_tweetとshowでidを使い分けたほうが、
可読性は高まりそうだけど動くからこれでいいかなー
localより後に書いた部分は消せないし、renderって難しい。

今回も長くなりましたが、以上になります!
ここまで読んでいただきありがとうございます!

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

Railsで画像を使用する

Railsで画像を使用する。

利用したい画像ファイルをapp/assets/imagesに入れておく。
画像を使用したいhtml.hamlファイルにて、

html.haml
= image_tag src= "/assets/sample.png", size: "50x50"

Railsで背景画像をcssで指定する。

背景として利用した画像ファイルをapp/assets/imagesに入れておく。
そしてcssファイルにて

css
background: image-url("sample.png");
background-size: cover;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フォームをGETで送信するときは送信したいパラメータをURLではなくhiddenタグに設定する必要がある、という話

はじめに:イントロダクション

以下のような単純なRailsのフォームがあったとします。

<%= form_tag foo_blogs_path(some_flag: 1) do %>
  <%= button_tag 'Foo' %>
<% end %>

コントローラのコードは次のようにsome_flagの値をnoticeに出力するようになっています。

class BlogsController < ApplicationController
  def foo
    redirect_to :blogs, notice: "Foo / some_flag: #{params[:some_flag]}"
  end
end

以下はFooボタンをクリックしたときの実行結果です。
フォームのURLに設定したsome_flag: 1の値が表示されています。

Screen Shot 2020-05-20 at 9.27.10.png

本題:困ったこと

上の例と同じようなことを以下のようなフォームでもやろうとしました。

<%= form_tag bar_blogs_path(some_flag: 1), method: :get do %>
  <%= button_tag 'Bar' %>
<% end %>

先ほどのコード例と違うのはフォームのHTTPメソッドがGETになっている点(method: :get)です。

コントローラ側のコードはほぼ同じです。

class BlogsController < ApplicationController
  def bar
    redirect_to :blogs, notice: "Bar / some_flag: #{params[:some_flag]}"
  end
end

しかし、Barボタンをクリックしてみると・・・

Screen Shot 2020-05-20 at 9.27.30.png

あれっ、some_flagの値が空になってる!

ログを見るとこんなふうになっていてsome_flagの値が渡されていません。

Started GET "/blogs/bar?button=" for 127.0.0.1 at 2020-05-20 09:13:49 +0900

出力されたHTMLを見てみると、action="/blogs/bar?some_flag=1"のようにsome_flagの値がちゃんと設定されているように見えます。

<form action="/blogs/bar?some_flag=1" accept-charset="UTF-8" method="get">
  <button name="button" type="submit">Bar</button>
</form>

なんでだろう、おかしいなあ・・・。
原因を突き止めるためにいろいろ試行錯誤したものの、some_flagの値をサーバーに送信することができず、この日は諦めて寝ることにしました。

翌日・・・解決しました!

次の日、デバッグを再開したところ、今度は原因と解決策がわかりました。
フォームをGETで送信する場合は、actionのURLではなくhiddenタグに送信したいパラメータを含める必要があるのでした。

<%= form_tag bar_blogs_path, method: :get do %>
  <!-- hiddenタグに送信したいパラメータを含める -->
  <%= hidden_field_tag :some_flag, 1 %>
  <%= button_tag 'Bar' %>
<% end %>

こうすればsome_flagの値をサーバーに送信することができます!

Screen Shot 2020-05-20 at 9.27.45.png

ログを見てもsome_flag=1が送信されていることがわかります。

Started GET "/blogs/bar?some_flag=1&button=" for 127.0.0.1 at 2020-05-20 09:22:21 +0900

これで解決です。めでたしめでたし。

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

ターミナルとは?-絶対パス&相対パス-

ターミナルとは

ターミナルとは、コマンドラインの一つで、PCに命令を与えるためのツールです。記述したコードのファイルを実行するのに必要となります。
コマンドラインとは、コマンドラインインターフェース(CLI)の略で、PC対してキーボードからコマンドという文字を打ち込んで操作を行う方法です。
これとは対照に、グラフィックを用いてPCの操作を行う方法をグラフィカルユーザーインターフェース(GUI)と言います。


コマンド

ターミナルでよく使うコマンドを紹介します。


cdコマンド
デイレクトりを移動するコマンドです。

lsコマンド
現在いるディレクトリの直下にあるファイルやフォルダを表示します。


ディレクトリとは
  • ディレクトリとはファイルの入れ物のようなものでフォルダを指します。 ディレクトリのは階層構造になっており、 その一番上の階層のディレクトリのことをルートディレクトリと言います。 自分が現在いるディレクトリのことをカレントディレクトリと言います。 また、ターミナルを開いた時に表示されるディレクトリのことをホームディレクトリと言います。Macでのデフォルトのホームディレクトリは/Users/ユーザー名です。



話戻ります。
cdコマンドを使ってディレクトリを行き来できるわけですが、cdコマンドに続けてパスを入力することによって目的のディレクトリに辿り着くことができます。

そのパスに記述の仕方には2通りあります。
・絶対パス
・相対パスです

絶対パス

  • 絶対パスとはルートディレクトリから記述するパスのことです。 /から記述するのが決まりです。
ターミナル.
$ cd /

###これでルートディレクトリに移動できます

MacのデフォルトのルートディレクトリはMacintosh HDだと思いますのでcd /でMacintosh HDに移動します。
lsコマンドで直下のディレクトリを確認すればパスがわからなくても確認して移動できます。

相対パス

相対パスは先頭に/をつけません。
現在いるディレクトリの直下にあるディレクトリから記述することによって移動することができます。

この記事を読んでいただきありがとうございました。

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

Railsで「Could not find a JavaScript runtime」というエラーが出てrails s ができなくなった

rails s ができない

久しぶりにRailsを触り、rails sでサーバー起動させると
長文のエラーが発生し、一番下に「Could not find a JavaScript runtime」と書いてありました。

解決策

調べたところ解決策は2つあるみたいです。
gem 'therubyracer' をGem fileに追記し bundle installを実行する。
・Node.jsをhttps://nodejs.org/en/からインストールする。

GitHub ExecJS
https://github.com/sstephenson/execjs
スクリーンショット 2020-05-16 1.42.21.jpg

私はNode.jsをインストールし
「rails s」できるようになりました。

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