20200802のRailsに関する記事は15件です。

No such file or directory @ rb_sysopen - /Users/○○/Gemfile.lock (Errno::ENOENT)で苦しんだ

railsコマンドを打とうとすると

$ rails ○○
Traceback (most recent call last):
        4: from bin/rails:3:in `<main>`
        3: from bin/rails:3:in `load`
        2: from /Users/user/[プロジェクト名]/bin/spring:10:in `<top(required)>`
        1: from /Users/user/[プロジェクト名]/bin/spring:10:in `read`
/Users/user/[プロジェクト名]/bin/spring:10:in `read`: No such file or directory @ rb_sysopen - /Users/user/[プロジェクト名]/Gemfile.lock(Errno::ENOENT)

こんなエラーが出てしまい号泣。

原因

railsコマンドでは、gemのバージョンも確認工程に含まれるためGemfile.lockの中身が参照される。
ただ今回、参照されるべきGemfile.lockがない状態なのでエラーが出てしまっている状況。

対処法

Gemfile.lockgemfileのバージョンを記録しておくもので、bundle installコマンドによって自動的に生成されるため、bundle installを実行する必要がある。

早速、アプリディレクトリ上にてbundle installを実行↓

$ pwd
/Users/○○/アプリ名

$ bundle install

その後、再度railsコマンドを入力してみる。
すると、無事にrailsコマンドが効く状態になってるはず!

まとめ

かなり初歩的な内容かもですが、自分は過去にこのエラーで相当時間を取られました...
同じエラーで苦しむ人が減れば幸いです!

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

rails開発の条件分岐のあれこれ

rails開発の条件分岐のあれこれ

rails でのwebアプリケーション開発であるといい記述をまとめてみました!

投稿に対して投稿者のみ編集・削除ができるようにしたい!

rails でSNSのようなアプリケーションを作るとき,投稿に対してユーザー誰もが編集したり
削除できるとダメですよね!
そんなとき便利なのが下の条件分岐です!

devise(Gem)をインストールしていうることを前提とします

post.rb
  belongs_to :user
user.rb
  has_many :posts
route.rb
  resources :post, only: [:show]
post_controller.rb
  def show
      @film = Post.find(params[:id])
  end
show.html.erb
  <% if @post.user_id == current_user.id %> #追記
    <%= link_to "編集する", edit_post_path(@post.id) %>
    <%= link_to "削除する", post_path, method: :delete %>
  <% end %> #追記

このように編集・削除の部分を条件分岐で
もし,投稿者のidがログイン中のユーザーidと一致したら
の意味を持つ,<% if @post.user_id == current_user.id %> でかこんであげるといいでしょう!

ログインしている人としていない人で記述を変えたい!

例えばこのようなことはないでしょうか?
ログイン前には,ヘッダーに新規登録・ログインのリンクを
ログイン後には,マイページやログアウトのリンクを
実装したい!
こんな時に便利なのが下の条件分岐です!

devise(Gem)をインストールしていうることを前提とします

今回,bootstrapを使用したナビゲーションバーを使用しています
bootstrapはこちら

layout/application.html.erb
  <% if user_signed_in? %>
      <nav class="navbar fixed-top navbar-expand-lg navbar-light">
        <a class="navbar-brand" href="/">ホーム</a>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav mr-auto">
            <li class="nav-item">
              <a class="nav-link" href="/post/new" style="color: white;">投稿する<span class="sr-only">(current)</span></a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="/users/<%= current_user.id %>">マイページ</a>
            </li>
            <li class="nav-item" >
              <%= link_to 'ログアウト', destroy_user_session_path, data: { confirm: "ログアウトしますか?" }, method: :delete, class:"nav-link"%>
            </li>
          </ul>
        </div>
      </nav>
  <% else %>
      <nav class="navbar fixed-top navbar-expand-lg navbar-light">
        <a class="navbar-brand" href="/home">トップ</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav mr-auto">
            <li class="nav-item">
              <a class="nav-link" href="/users/sign_up" style="color: white;">新規登録</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="/users/sign_in" style="color: white;">ログイン</a>
            </li>
          </ul>
        </div>
      </nav>
  <% end %>

<% if user_signed_in? %>の部分が重要です!
これは,「もしユーザーがログインしていたら」という条件分岐なのです!
<% else %>(そうでなければ→「ログインしていなかったら」)との組み合わせでさらなる力を発揮します!

画像投稿でnilのエラーを吐いてしまう

**cloudinaryを使った画像投稿を実装できていることとします

今回は画像投稿を例に出していますがその他にも,nilのときどうしよう..という場合にこれが使えます!

db/migrate/OOOOOOOOOOOOOO_add_image_to_posts.rb
  def change
    add_column :posts, :image, :string
  end
post_controller.rb
  def show
      @film = Post.find(params[:id])
  end
show.html.erb
  <% if @post.image.present? %>
      <%= image_tag @post.image_url, :size =>'150x150', class: "img_fluid rounded-circle" %>
   <% end %>

<% レコード.カラム.present? %>の部分が大事です!
これによってnilではなく存在していれば表示される条件分岐となります!

[補足]
画像サイズを正方形にして,bootstrapによって画像をTwitterのプロフィール画像のように丸くしています

最後に

ここまで3つの個人的によく使用する条件分岐をについて記述してみました!
間違い等ございましたら指摘してください!

条件分岐(if)文はプログラミングの中でも共通で大事なものです!
実際に手を動かして理解してみましょう!

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

A server is already running(rails sのプロセスが切れない)をさっさと解決する

サーバーを起動しようとすると以下がでるときの対処法を数種類まとめました!
どれかで解決できる(はず)。

❯ rails s
=> Booting Puma
=> Rails 5.0.7.2 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
A server is already running. Check プロジェクト名/tmp/pids/server.pid.
Exiting

何種類もあるため、上手くいかないときは順番に試してみるコトをおすすめします。

まず確認すること

たまにMac標準のターミナルでサーバー起動状態 & VSCodeなどのテキストエディタ上のターミナルでサーバー起動しようとしている方がいるので、
まずは別のターミナルで既にサーバーを動かしていないかを確認しましょう!

パターン①

railsのプロセスを削除する

$ rails s
=> Booting Puma
=> Rails 5.0.7.2 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
A server is already running. Check プロジェクト名/tmp/pids/server.pid.
Exiting

$ ps aux | grep rails
user   28321 s001  S+     0:00.00 grep rails

$ kill -9 28321

$ rails s
→ 解決

パターン②

ポート番号3000番のプロセスを削除する

$ lsof -wni tcp:3000
COMMAND   PID  USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
ruby    28295  user   21u  IPv4 0x77d8a30cabb79cc9      0t0  TCP 127.0.0.1:hbci (LISTEN)
ruby    28295  user   22u  IPv6 0x77d8a30cac93f9f9      0t0  TCP [::1]:hbci (LISTEN)

# 「COMMAND」に「ruby」と書いてある行のPIDをコピーして処理を停止(今回は28295)
$ kill -9 28295

$ rails s
→ 解決

パターン③

サーバー起動の際に使用するIDを削除する

$ rm /tmp/pids/server.pid

$ rails s
→ 解決

このファイルの場所は[アプリ名]/tmp/pids/server.pidに入っているので、パスを指定して削除します。
本来、サーバーを終了するとこのファイルは削除されますが、残ったままでエラーになっている可能性があるみたい。
server.pidはサーバー起動や終了で勝手に作られたり削除されたりするので、普段は気にしなくて良いです。

まとめ

頻繁に出てきたkill -9 ○○Linuxコマンドの一つで、プロセスを終了するためのものです。
個人的には、パターン②ですんなりプロセスを終了出来ることが多い気がします。

サーバーの立ち上げで足止めをくらうと萎えるので、さくっと解決してください!

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

RailsでOpenWeatherMapから天気予報を取得する

初めに

Qitta初投稿です!
初心者なので至らぬ点も多いかと思いますが、暖かくコメント頂けると嬉しいです!

概要

  • 無料のapiを提供しているOpenWeatherMapのapiを叩くことで、全国各地の天気予報を取得する。
  • HTTPリクエストはhttpclientを使用し、rake taskにすることで定期的に叩けるように実装する。

開発環境

ruby: 2.7.1
rails: 6.0.3.2

手順

  1. API KEYの取得
  2. 取得したい都市のCITY IDを取得
  3. Cityテーブルの作成・保存
  4. HTTPリクエストの実装
  5. WeatherForecastテーブルの作成・保存

1. API KEYの取得

OpenWeatherMapのホームページにアクセスし、Sign inからCreate an accountでアカウントを作成しましょう。送られてくるメールから有効化するとAPI KEYが送られてきます。

これを環境変数なりcredentialsなりに保存しておきます。今回はcredentialsを利用していきます。
credentialsに関してはこちらの方の記事がとても参考になります。

EDITOR=vi bin/rails credentials:edit
credentials.yml.enc
open_weahter:
    appid: <API_KEY>
    uri: https://samples.openweathermap.org/data/2.5/forecast

なお、今回は3時間毎の天気を取得しようと思うので、APIドキュメントを参考にリクエストを送るURIを取得しています。

無料枠でも取得できる天気の種類は豊富にあるようです!APIドキュメントを色々と探してみると面白いです。

2. CITY IDの取得

APIドキュメントからcity.list.jsonをダウンロードします。スクリーンショット 2020-08-02 15.54.09.png

このファイルから、取得したい都市のCITY IDを取得していきます。中身一部抜粋です。
ちなみにlonは経度、latは緯度を指します。

city.list.json
  {
    "id": 1850147,
    "name": "Tokyo",
    "state": "",
    "country": "JP",
    "coord": {
      "lon": 139.691711,
      "lat": 35.689499
    }
  },

私はこのid取得を泣く泣く手作業でしました...
同じ名前の都市でも経緯度が違うものが混じっているので注意が必要です!

エクセルやmacならnumbersなどにリストアップしてCSVに変換すると良いと思います!
ちなみに私が作成したCSVはこんな感じです。一部カラムを削ってます。

db/csv/cities.csv
札幌,2128295
青森,2130658
盛岡,2111834
仙台,2111149
秋田,2113126
山形,2110556
福島,2112923
水戸,2111901
宇都宮,1849053
前橋,1857843
さいたま,6940394
千葉,2113015
東京,1850147
横浜,1848354
新潟,1855431
富山,1849876
金沢,1860243
福井,1863983
山梨,1848649
長野,1856215
岐阜,1863640
静岡,1851715
名古屋,1856057
津,1849796
大津,1853574
京都,1857910
大阪,1853909
神戸,1859171
奈良,1855612
和歌山,1926004
鳥取,1849890
松江,1857550
岡山,1854383
広島,1862415
山口,1848689
徳島,1850158
高松,1851100
松山,1926099
高知,1859146
福岡,1863967
佐賀,1853303
長崎,1856177
熊本,1858421
大分,1854487
宮崎,1856717
鹿児島,1860827
那覇,1856035

3. Cityテーブルの作成・保存

seeds.rbtaskにコードを書いて、データベースに保存していきます。今回はlib/tasks以下に実装しました。CITY IDはカラム名をlocation_idとして保存しています。

import_csv.rake
  desc 'Import cities'
  task cities: [:environment] do
    list = []
    CSV.foreach('db/csv/cities.csv') do |row|
      list << {
        name: row[0],
        location_id: row[1],
      }
    end

    puts 'start creating cities'
    begin
      City.create!(list)
      puts 'completed!'
    rescue ActiveModel::UnknownAttributeError
      puts 'raised error: unknown attributes'
    end
  end

CSV.foreachメソッドで先ほど作成したcities.csvを一行ずつ読み込みます。row[0]で1列目の都市名、row[1]で2列目のCITY IDが取得できるので、ハッシュの配列を作成してCity.create!でデータベースに保存しています。

4. HTTPリクエストの実装

レスポンスの解析

HTTPリクエストを実装する前に、まずは、レスポンスのJSONファイルを解析していきます。

APIドキュメントに各項目について詳しく説明が書かれているので、それを参考に欲しいデータのキーを取得していきます。
1つのCityに関するリクエストは以下のようなJSON形式で返ってきます。
(curlコマンドや、VScodeをお使いの方はREST Client等で試してみると良いと思います。)

example_resopnse.json
{
  "cod": "200",
  "message": 0,
  "cnt": 40,
  "list": [
    {
      "dt": 1578409200,
      "main": {
        "temp": 284.92,
        "feels_like": 281.38,
        "temp_min": 283.58,
        "temp_max": 284.92,
        "pressure": 1020,
        "sea_level": 1020,
        "grnd_level": 1016,
        "humidity": 90,
        "temp_kf": 1.34
      },
      "weather": [
        {
          "id": 804,
          "main": "Clouds",
          "description": "overcast clouds",
          "icon": "04d"
        }
      ],
      "clouds": {
        "all": 100
      },
      "wind": {
        "speed": 5.19,
        "deg": 211
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2020-01-07 15:00:00"
    },

今回は以下の項目を使ってテーブルを作成しています。

  • list
    • main
      • feels_like: 体感気温
      • temp_max: 最高気温
      • temp_min: 最低気温
    • weather
      • id: 天気ID
    • rain
      • 3h: 降水量

降水がある場合のみ、rainがlistに追加されます。

なお、天気IDはこちらを参考にしてください。OpenWeatherMapでは、IDによって天気を分類しています。descriptionから天気を取得することも可能です。しかし種類が多いため、今回の実装ではデータベースには天気IDを保存しておき、メソッドで天気を割り振るようにしています。

HTTPリクエストの実装

まずは、httpclientをGemfileに追加し、bundle installします。

gem 'httpclient', '~> 2.8', '>= 2.8.3'

次にlib/api/open_weather_map/request.rbを作成します。
ここまで深くする必要はないかなと思ったのですが、このapiに関して他のクラスを実装したり、他のapiクラスを実装することを考慮してこのようなファイル配置にしました。

lib以下はデフォルトではtask以外読み込まれないので、config/application.rbに、以下の設定が必要です。eager_load_pathsなので本番環境も大丈夫です。

config/application.rb
config.paths.add 'lib', eager_load: true

おいおいこれはあかんやろ、っていう箇所があればご指摘頂けるととても嬉しいです。ファイルの配置が今回の実装で最も悩みました...

リクエストを保存するWeatherForecastテーブル↓

WeatherForecast
temp_max float
temp_min float
temp_feel float
weather_id int
rainfall float
date datetime
aquired_at datetime

以下が実装したRequestクラスです。

request.rb
module Api
  module OpenWeatherMap
    class Request
      attr_accessor :query

      def initialize(location_id)
        @query = {
          id: location_id,
          units: 'metric',
          appid: Rails.application.credentials.open_weather[:appid],
        }
      end

      def request
        client = HTTPClient.new
        request = client.get(Rails.application.credentials.open_weather[:uri], query) # 戻り値は3時間ごとのデータ5日分
        JSON.parse(request.body)
      end

      def self.attributes_for(attrs)
        rainfall = attrs['rain']['3h'] if attrs['rain']
        date = attrs['dt_txt'].in_time_zone('UTC').in_time_zone

        {
          temp_max: attrs['main']['temp_max'],
          temp_min: attrs['main']['temp_min'],
          temp_feel: attrs['main']['feels_like'],
          weather_id: attrs['weather'][0]['id'],
          rainfall: rainfall,
          date: date,
          aquired_at: Time.current,
        }
      end
    end
  end
end

initializeでクエリストリングを設定しています。今回のリクエストで必要なクエリストリングは、CITY IDを示すlocation_idとAPI KEY, そして気温の表示を摂氏表示に変更するためにunits: 'metric'を加えています。

返ってきたリクエストをデータベースに保存できる形に直すためにattributes_forメソッドをクラスメソッドにしています。

注意が必要なのは、降水量と予報日付です。

  • 降水量は、降水がない場合は項目が存在しません。なのである場合のみ取得するように条件分岐しています。
  • 予報日付に関しては、タイムゾーンに注意が必要です。OpenWeatherMapのタイムゾーンはUTCなので、JTCに変換してから保存しています。

タイムゾーンの扱いについてはこちらの記事が参考になります。

5. WeatherForecastテーブルの作成・保存

rake taskの作成

データベースへの保存・更新は定期的に実行したいのでrake taskに書いていきます。
と言っても、先ほどのRequestクラスでほぼメソッドを書いたので、後はそれを使うだけです。

open_weather_api.rake
namespace :open_weather_api do
  desc 'Requests and save in database'
  task weather_forecasts: :environment do
    City.all.each do |city|
      open_weather = Api::OpenWeatherMap::Request.new(city.location_id)

      # リクエスト上限:60回/min
      response = open_weather.request

      # 3時間ごとのデータ2日分を保存
      16.times do |i|
        params = Api::OpenWeatherMap::Request.attributes_for(response['list'][i])
        if weather_forecast = WeatherForecast.where(city: city, date: params[:date]).presence
          weather_forecast[0].update!(params)
        else
          city.weather_forecasts.create!(params)
        end
      end
    end
    puts 'completed!'
  end
end

今回は、3時間毎のデータを2日分ずつデータベースに保存、更新する仕様にしました。
ポイントはリクエスト上限と、データの作成なのか更新なのかという点です。

  • リクエスト上限は無料プランでは60calls/minです。登録している都市が60を超える場合は、分けてリスエストを送る必要があります。今回は47なので問題ありません。
  • presenceメソッドはpresent?メソッドを呼び出して、trueだった場合はレシーバー自身を返すメソッドです。同じ都市の同時刻に関する予報がすでにデータベースに存在する場合はupdate!を、存在しない場合はcreate!を呼び出しています。

最後に

保存したweather_idに対応する天気のアイコンを用意すると天気予報らしい見た目になると思います!
cronや、herokuならheroku schedularなどで定期的にapiを叩くようにしておくと良いかと思います!

こんな感じで表示でしました!
スクリーンショット 2020-08-02 18.26.45.png

長々とした文章を読んで頂きありがとうございました!

参考

https://openweathermap.org/api
https://qiita.com/yoshito410kam/items/26c3c6e519d4990ed739

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

Route53でドメインを登録した際にNginxのページが表示されてしまう時の対処

経緯

・Railsのアプリケーションは既にAWSへデプロイ済み
・せっかくならIPアドレスのままではなく、独自ドメインで表示したい!

行ったこと

1.お名前.comでドメインを購入。
2.Route53でホストゾーンを作成し、ネームサーバーをお名前.comからRoute53へ変更。
3.レコードセットを作成し、ドメインに紐づくIPアドレスを登録

起こった問題

AWSのコンソール、レジストラ側の設定が全て完了して、いざ取得したドメインで自分のサイトが開ける。と思ったが、Nginxのページが出てしまいアプリケーションへアクセス出来ない。
スクリーンショット 2020-08-02 18.22.25.png

対処

・Nginxの設定で新しいドメインのURLを明記する

EC2へログインして、

[ec2-user@ip-xxxx ~]$ sudo vim /etc/nginx/conf.d/rails.conf
rails.conf
upstream app_server {
  server unix:/var/www/<アプリケーション名>/tmp/sockets/unicorn.sock;
}

server {
  listen 80;
  # ここのElasticIPとなっている箇所を、
  server_name xxx.xxx.xx.xx; 
  # 取得したURLへ変更する
  server_name <取得したドメインのアドレス>; 


  client_max_body_size 2g;

  root /var/www/<アプリケーション名>/public;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @unicorn;

  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_server;
  }

  error_page 500 502 503 504 /500.html;
}

最後にNginxを起動させて設定ファイルの変更を読み込ませる

[ec2-user@ip-xxxx ~]$ sudo systemctl start nginx
[ec2-user@ip-xxxx ~]$ sudo systemctl reload nginx

あとは、Capistranoを走らせるついでにUnicornを再起動したら、無事に自分のアプリケーションが開けました!

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

jquery.raty rails導入

はじめに

  • Golf GearのReviewサイトを作成中。
  • Reviewの評価を星で表示する。
  • 公式サイトはなぜかアクセスできないので、githubや画像をそれぞれ拾ってきて、表示できるようにする。 image.png

jquery.raty.jsファイルとstar画像を配置

image.png

  • application.jsでrequireする。
app/assets/javascripts/application.js
//= require jquery.raty.js

Review評価数を星に置き換える

app/views/reviews/_review.html.erb
<div class="review-rating" data-score="<%= review.rating %>"></div>
<p><%= review.comment %></p>
app/views/gears/show.html.erb
<%# 末尾に記載 %>
<script>
  $('.review-rating').raty({
    readOnly: true,
    score: function() {
      return $(this).attr('data-score');
    },
    path: '/assets/'
  });
</script>
  • 今回、_review.html.erbdata-scoreを読み込み、render @gear.reviewsで呼び出しているので、gears/show.html.erbにjquery発火イベントを記載。

Review投稿Formにstarで評価する

app/views/reviews/_form.html.erb
<%= simple_form_for ([@gear, @gear.reviews.build]) do |f| %>
  <div id="rating-form">
    <label>Rating</label>
  </div>
  <%= f.input :comment %>
  <%= f.button :submit %>
<% end %>

<script>
  $('#rating-form').raty({
    path: '/assets/',
    scoreName: 'review[rating]'
  });
</script>

https://gyazo.com/56bec592eeb6da5f719aa162dc26feed

投稿Reviewの平均を表示

app/controllers/gears_controller.rb
def show
  if @gear.reviews.blank?
    @average_review = 0
  else
    @average_review = @gear.reviews.average(:rating).round(2)
  end
end
# @average_reviewのインスタンス変数を設定
app/views/gears/show.html.erb
<div class="col-sm-8">
  <h2><%= @gear.name %></h2>
  <h3><%= @gear.maker.name %></h3>
  <h2>Average Rating</h2>
  <div class="average-review-rating" data-score=<%= @average_review %>>
  <span>Based on <%= @gear.reviews.count %> Reviews</span>
  <h4><%= @gear.club.name %></h4>
  <p><%= @gear.description %></p>
</div>
<%# data-scoreに先ほど設定した@average_reviewのインスタンス変数を当てる。 %>

<script>
  $('.average-review-rating').raty({
    readOnly: true,
    path: '/assets/',
    score: function() {
      return $(this).attr('data-score')
    }
  });
</script>
<%# jquery発火条件記載 %>

image.png

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

ファイル保存先をS3に設定後、RailsアプリをHerokuにpushすると失敗するエラーについて

エラー発生の経緯

本番環境でファイルを投稿後、1日程経つとファイルが表示されなくなる問題が発生していたため、HerokuへデプロイしていたRailsアプリを更新し、ファイルアップロード先をAWS S3に変更しました。

ローカル環境では適用に成功しましたが、git push heroku masterコマンドで本番環境に適用しようとするとエラーが発生しました。

エラー内容

ターミナル
Caused by:
ArgumentError: key must be 16 bytes

結論

以下のコマンドで環境変数を設定するとエラーが解消されました。
heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

原因

元々下記のようなエラーが出ており、解消のために
heroku config:set RAILS_MASTER_KEY=`rake secret`
と言うコマンドで環境変数を設定していました。

ターミナル
-----> Preparing app for Rails asset pipeline
       Running: rake assets:precompile
       Missing encryption key to decrypt file with. Ask your team for your master key and write it to /tmp/build_6edee55a_/config/master.key or put it in the ENV['RAILS_MASTER_KEY'].
 !
 !     Precompiling assets failed.
 !
 !     Push rejected, failed to compile Ruby app.
 !     Push failed

`rake secret`は secret.yml を使っている場合のコマンドであり、credentials.yml.enc (Rails5.2以降) を使っている場合はRAILS_MASTER_KEY には`cat config/master.key`と設定する必要がありました。

参考:Ask your team for your master key and put it in ENV[“RAILS_MASTER_KEY”] on heroku deploy

解決までに行ったこと

エラー解消方法に気づく直前まで、最後の方で出力されていた以下のようなエラーに注目していました。
出力されている通りPrecompiling assets failedPush rejectedfailed to compile RubyPush rejectedといったワードで検索してエラー解消に奮闘していましたが、解決しませんでした。

ターミナル
remote:        /tmp/build_0a1646cf/vendor/bundle/ruby/2.5.0/gems/sprockets-rails-3.2.1/lib/sprockets/rails/task.rb:62:in `block (2 levels) in define'
remote:        Tasks: TOP => environment
remote:        (See full trace by running task with --trace)
remote: 
remote:  !
remote:  !     Precompiling assets failed.
remote:  !
remote:  !     Push rejected, failed to compile Ruby app.
remote: 
remote:  !     Push failed
remote: Verifying deploy...
remote: 
remote: !       Push rejected to (アプリ名).
remote: 
To https://git.heroku.com/(アプリ名).git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://git.heroku.com/(アプリ名).git'

エラー解消に繋がらなかった作業

1. heroku git push先を再設定
herokuのリモートリポジトリをセット
 git remote set-url heroku https://git.heroku.com/(アプリ名).git
heroku git push先確認
 git remote -v

2. config/application.rbの設定変更
config/application.rb の config.assets.initialize_on_precompile = trueconfig.assets.initialize_on_precompile = false に変更
参考 RailsでアプリをHerokuにあげる時のエラー各種
   Herokuにpushしたらprecompile assets faild.が。pushできたと思ったらApplication Errorが。解決するまで。

3. assets:precompileの設定変更
RAILS_ENV=production bundle exec rake assets:precompile
参考 Herokuにデプロイしたときの「Precompiling assets failed.」エラーについて

4. buildpackを手動設定してデプロイ
heroku buildpacks:set heroku/ruby
参考 Heroku へのデプロイに参った!

5. Herokuを消して、作り直し
Herokuのアプリ管理画面の Settings → Delete app からアプリを削除
git remote rm heroku でremoteにあるherokuを削除
heroku create (アプリ名) でアプリを作り直す ※アプリ名は元と同じ
git push heroku masterでデプロイ
参考 herokuにデプロイできない

6. 通常にデプロイできていた部分までrevertして再デプロイ
GitHub Desktop の History でコミットを1つずつ右クリックして Revert this Commit

7. 強制プッシュでデプロイ
git push heroku --force master

学んだこと

・ログは上の方もしっかり読む(最後の方のログに惑わされない)
・エラー発生直前に行った作業から原因を推測する(関係の薄いファイルはいじり過ぎない)
credentials.yml.encconfig/master.key、 環境変数の仕組みを理解することが重要
・herokuはアプリを消すと再度同じURLでアプリ作成、デプロイできる
・Git で管理されないファイルを変更したりして環境変数が関わるエラーが発生した際は revert や 再デプロイしても本番環境のエラーは直らない

特にPrecompiling assets failedといったエラーに関する情報は沢山ありますが、原因は様々なため、エラー内容をしっかり見ることが非常に大切だと実感しました。

※最後の方のエラーばかり見て解決に何時間もかかっていましたが
「ArgumentError: key must be 16 bytes」がエラーの本体だと気づいた後、30分もかからずに解決しました!

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

[rails]画像の投稿方法

画像投稿方法

画像の投稿方法はこんな感じの手順です。

・refileをgemfileに追加
・image_idカラムを追加
・attachmentメソッドを追加する
・Strong Parametersにimage_idを追加する
・viewファイルにf.attachment_fieldを埋め込む

1.refileをgemfileに追加

refileの役割
・画像を簡単に組み込むことができる。
・サムネイルを生成できる。
・ファイルのアップロード先を設定できる。

refile-mini-magickは画像をサイズを変更するためのgemです。

# 画像投稿用gem
gem "refile", require: "refile/rails", github: 'manfe/refile'
# 画像加工用(サイズ調整など)gem
gem "refile-mini_magick"

bundle installは忘れずに。

$ bundle install

2.image_idカラムを追加

Userテーブルにimage_idカラムを追加します。

$ rails g migration AddImageIdToUsers image_id:string

これも忘れてはいけません。
$ rails db:migrateでデータベースに反映。

$ rails db:migrate

3.attachmentメソッドを追加する

Refileを使うには、attachmentメソッドをモデルに追加する必要があります。
attachmentメソッドとは、refileが指定のカラムにアクセスするために必要です。
これによりDBに存在する画像を取得したりアップロードが可能となります。
カラム名はimage_idですが、ここでは_idは不要です。

app/models/user.rb
class User < ApplicationRecord
  attachment :image
end

4.Strong Parametersにimage_idを追加する

class UserController < ApplicationController
  #省略

  private
  def list_params
    params.require(:user).permit(:name, :email, :image)
 end
end

5.viewファイルにf.attachment_fieldを埋め込む

次に画像を投稿するページに以下の通り、記述します。

<%= f.attachment_field :image %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails 本番環境のエラーをブラウザで表示する方法

結論

下記を変更するだけで本番環境でもブラウザ上でエラーを表示することが可能である。

//config/enviroments/production.rb
config.consider_all_requests_local = false 変更前
config.consider_all_requests_local = true 変更後

注意点

本番環境の詳細なエラー内容がユーザに見えてしまうのはよくないので、
エラー原因が特定できれば元に戻すのが良いかと思います。

開発環境ではエラーが存在する場合、ブラウザ上で表示してくれる。
しかし、本番環境ではproduction.rbのログを見たり、unicornやnginxのエラーログを見るくらいしか知らなかった。

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

[Rails] Serviceクラスの設計で悩んだこと

RailsでServiceクラスの設計について考えたことを、まとめておきます。
(まだ最適解には至ってないです)

会員登録機能を作る

分かりやすいように例として、よくあるユーザの会員登録機能を実装してみます。
パスワードの暗号化、認証urlの発行、認証メールの送信など、Controllerに全て書くとfatになってしまいそうです。こういう処理の流れは1つの処理の流れとしてどこかにまとめて書いた方が良さそうです。

Serviceクラスの役割とは

自分の中でServiceクラスの役割として、

  • ロジックの集約
  • Controllerの肥大化を防ぐ
  • ユースケースを表現する

があると思っています。会員登録はユースケースとして捉えることができるので、Serviceクラスに当てはまりました。

いざ設計

では、具体的に設計を考えてみます。まず、漠然と呼び出しのイメージを考えてみます。

registration_controller.rb
class RegistrationController < ApplicationController
  def create
    SignUpService.sign_up(email, password, confirm_password)
    if # 成功したかの判定
      redirect_to user_confirm_path
    else
      render 'new'
    end
  end
end

大まかですが呼び出し側は、メソッド1発呼び出して全ての処理ができるといいなと考えました。

次にSerivice側です。
こっちの設計にとても悩みました。

moduleにしてみる

moduleとclassの違いは、インスタンス化できるかどうかです。
処理の塊にしたかっただけなので、インスタンス化する必要はなさそうだと判断し、まずはmoduleに決めました。

sign_up_service.rb
module SignUpService
  def sign_up(email, password, confirm_password)
    validate_params(email, password, confirm_password)
    user_create(email, password) 
    send_confirmation_mail(email)
  end

  private 

  def validate_params(email, password, cofirm_password); end
  def user_create(email, password); end
  def send_confirmation_mail(email); end
end

こんな感じでsign_upメソッドを呼び出すと順々に処理がされるようなイメージになりました。
外からはsign_upメソッドしか見えないので、使い方が分かりやすくて良さそうです。

moduleでの問題点

いざ上記の設計で作っていくと、「状態を持たせたく」なってきました。
例えば、処理が成功したかどうか、エラーメッセージなどです。
moduleでも実現できそうですが、classにしてnewしてインスタンス変数に処理結果を持たせた方がシンプルでいいのではないかと思い始めました。

やっぱclassにしてみる

やっぱclassで大枠を作ってみました。

sign_up_service
class SignUpService
  attr_reader :success, :error_messages

  def initialize(email, password, confirm_password)
    # インスタンス変数へ代入 
  end
  
  def sign_up
    return unless validate_params
    user_create
    send_confirmation_mail
  end

  private

  def validate_params
    if @password != @confirm_password
      error_messages.push('パスワードが一致しません') 
      success = false
      return false
    else
      ...
    end
  end
  def user_create; end
  def send_confirmation_mail; end
end

@success@error_messagesなどのインスタンス変数を持たせ、処理が終わるとそれぞれ結果を代入します。

registration_controller.rb
class RegistrationController < ApplicationController
  def create
    sign_up_service = SignUpService.new(email, password, confirm_password)
    sign_up_service.sign_up
    if sign_up_service.success
      redirect_to user_confirm_path
    else
      @error_messages = sing_up_service.error_messages
      render 'new'
    end
  end
end

呼び出し側では、インスタンスを作成しsign_upメソッドを呼び出した後に、インスタンス変数の中身を取り出すことで処理結果が分かります。使い勝手は良さそうです。
ただ、インスタンス変数への代入が複数箇所に及ぶのが気になるところではあります。また、SignUpServiceでsign_upメソッドを呼び出すのもなんだかなという気がします。

結果

いろんな記事を参考にしつつ、色々迷いがありましたが自分の中でこの形に落ち着きました。
(具体的な処理は省略しています)

sign_up_service
class SignUpService
  attr_reader :success, :error_messages

  def initialize(email, password, confirm_password)
    @email = email
    ... # 省略
    @error_messages = []
  end
  
  def call
    return unless validate_params
    user_create
    send_confirmation_mail
  end

  private

  def validate_params
    if @password != @confirm_password
      error_messages.push('パスワードが一致しません') 
      success = false
      return false
    else
      ...
    end
  end
  def user_create; end
  def send_confirmation_mail; end
end
registration_controller.rb
class RegistrationController < ApplicationController
  def create
    sign_up_service = SignUpService.new(email, password, confirm_password)
    sign_up_service.call
    if sign_up_service.success
      redirect_to user_confirm_path
    else
      @error_messages = sing_up_service.error_messages
      render 'new'
    end
  end
end

まとめるとこのようになります。

Serviceクラス

  • classにする
  • 外から見えるのはcallメソッドのみにする
  • attr_readerで状態を持たせる
  • 処理結果をインスタンス変数に格納するようにする

呼び出し側

  • インスタンス化する
  • インスタンスに対してcallメソッドを呼び出す
  • インスタンスの属性を見て処理をする

処理結果をインスタンス越しに取得できるのが便利になりました。

課題

課題点として

  • 呼び出しがnewしてcallしてと冗長
  • class内でインスタンス変数に代入する処理が散らばっている(代入し忘れや予期せぬ上書きなどが起こりそう)

があげられそうです。まだまだ改善の余地がありそうです。
もっと調べて固めていきたいです。

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

Rails testで「 FATAL: Listen error: unable to monitor directories for changes.」のエラー

前提

Rails 6.0.3
ruby 2.6.3

本題

$rails test

のコマンドを実行した時に以下のエラーが発生。

$ rails test
FATAL: Listen error: unable to monitor directories for changes.
Visit https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers for info on how to fix this.

1つのユーザIDに対して生成できるinotifyのインスタンスの数の上限に達してしまったようなことが書かれている?のではないかと思いました。
エラー内容に書いている通り、Githubを確認しコマンドを入力し解決。

解決方法

ユーザーインスタンスを確認。

$ cat /proc/sys/fs/inotify/max_user_instances

inotifyのインスタンス上限を変更。

$ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf

設定を反映。

$ sudo sysctl -p

再びコマンド実行。

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

【Rails】一意性を保つバリデーションの実装

一意性バリデーションの実装に必要なタスク

  1. アプリ側に記述(uniqueness: true)
  2. データベース側に記述(unique: true)

アプリ側に記述

models/user.rb
validates :email, uniqueness: true

データベース側に記述

$ rails g migration add_column_to_users
add_column_to_users.rb
def change
  add_index :users, :email, unique: true
end
$ rails db:migrate

テーブルのカラムに一意性を持たせるには、インデックスの作成も必要になる。
理由は、全てのデータを検索することで、過去のデータと重複しているか確認できるため。

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

オリジナルのアプリを完成させるまでの解説

環境

macOS 10.15.5
Rails 5.2.4.2
Docker 19.03.12

概要

オリジナルアプリの概要はCRUDのシステムを意識した簡易的なメモアプリの開発です。
簡易的なアプリと言っても実用性には欠かないよう注意を払いました。行数が増えてもレイアウト崩れが起きないように実装し、文字数制限や空白の項目が新規作成されないようにも設定しました。

Dockerを用いてフレームワークはRailsを使用しました。そしてHerokuにより公開をしています。

オリジナル性について

HTMLに関してはclass名などは学習サイトをヒントに用いて、デザインは外観は学習サイトをヒントにしましたが実装は全て自走で行いました。
Railsの機能の実装に関して事前に学習サイトを何度も繰り返し行い、CRUDの機能を頭に入れてから今回の作成を行いました。そのため短いコードなどはなるべくサイトなどを参考にせず、自分で覚えた内容で実装し、コマンド操作は基本的に全て頭に入れた内容のみで行いました。

サイトについて

スクリーンショット 2020-08-01 23.23.27.png
一覧ページをトップページとして定めメモの項目が個々の詳細が表示できるようにリンク指定をしています。その際に新規投稿された項目が最上部に移動するように実装しました。

lists_controller.rb
class ListsController < ApplicationController
  def index
    @lists = List.all.order(created_at: :desc)
  end

新規作成ページ

スクリーンショット 2020-08-01 23.23.45.png
新規作成ページでは空白の内容と141文字以上の内容が実行されないように設定しています。

list.rb
class List < ApplicationRecord
  validates :content, {presence: true, length: {maximum: 140}}
end

詳細ページ

スクリーンショット 2020-08-01 23.24.43.png
メモ一覧の個々の項目のリンクから移動すると個々の詳細ページが閲覧できます。そこから「編集」と「削除」を実行することができます。

編集ページ

スクリーンショット 2020-08-01 23.24.55.png
編集ページではページを開いたときに編集前の内容が表示されるように実装しています。

edit.html.erb
<div class="form-bady">
  <textarea name="content"><%= @list.content %></textarea>
  <input type="submit" value="編集">
</div>
lists_controller.rb
def edit
  @list = List.find_by(id: params[:id])
end

オリジナルアプリを作るにあたり意識したこと

わたしは自走できるエンジニアとしてスキルを身に付けたい!という思いからどんなやり方がそんなエンジニアに近づける方法なのか良く自問自答をします。今回このシンプルなメモアプリを作成しようと考えたのはRailsのCRUDの機能やMVCは完璧に落とし込もうと考えたのでこのシンプルさにまとめてみました。
今後はログイン機能やユーザーごとのアクセス権限を設けたページの作成などの機能を有したアプリの作成などにも取り組みたいです!

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

railsで動画を投稿する

今回、初投稿させていただきます。
少し、見にくかったりしてもご了承お願いします。

今回は、railsで動画アップロード機能の備忘録となっております。
個人的にFFmpegを使用した動画アップロードが分かりにくかったので、なるべく分かりやすく説明できればと思います。

環境

Ruby 2.6.5
Rails 6.0.3.2

早速取り掛かっていく

ターミナル
rails new RailsApp

ターミナルでRailsAppを作ります。

Gemfile
gem 'carrierwave'
gem 'mini_magick'

Gemfileに追記

ターミナル
rails bundle install

ターミナルでbundle installします。

ターミナル
rails g uploader video
rails g scaffold post video:string

ターミナルでuploaderとscaffoldを作成

ターミナル
rails db:migrate

忘れずにマイグレーション

app/models/post.rb
mount_uploader :video, VideoUploader

post.rbに追記

app/uploaders/video_uploader.rb
def extension_whitelist
   %w(jpg jpeg gif png MOV wmv mp4)
end 

38行目からコメントを外して、追記

views/posts/_form.html.erb
<div class="field">
    <%= form.label :video %>
    <%= form.file_field :video, :accept => 'video/*' %>
</div>

text.fieldになっていると思うので、file.fieldに書き換える。

app/views/posts/show.html.erb
<p>
  <%= link_to @post.video_url.to_s do %>
   <%= video_tag(@post.video.to_s) %>
  <% end %>
</p>

<%= @post.video %>になっていると思うので書き換えます。
<%= link_to @post.video_url.to_s do %>を書かないと動画をクリックしても再生されません。

ありがとうございました

動画アップロードに悪戦苦闘したので、皆さんが簡単にできたら幸いです。

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

has_manyとbelongs_toの使い方

プログラミングの勉強日記

2020年8月2日 Progate Lv.226
has_manybelongs_toの使い分けに戸惑った。has_manyのときに複数形にするのを忘れてしまい、うまく実行されなかったので、has_manybelongs_toについてまとめる。

関連付け(アソシエーション)とは

 2つのテーブルを関連付けさせること。テーブル間の関係をモデル上の関係として操作する仕組み。
 テーブル同士の関係には「1:1」「1:多」「多:多」の3つの関係が存在する。今回は「1:1」「1:多」の関係を表すときに使うhas_manybelongs_toについてまとめる

 今回は、UserテーブルのidをPostテーブルと関連付けをして、誰の投稿かわかるようする。

has_manyとは

 has_manyは「1:多」の関連付けを表し、〇〇が複数の〇〇を所有しているという関係を表す時に使う。関連付けをすることによって、データをまとめて扱えるようになるので、より効率的にデータベースを操作することができる。

models/user.rb
class User < ApplicationRecord
  has_many :posts
end

 has_manyを使うときは複数形にする

belongs_toとは

 belongs_toは「1:1」の関連付けを表し、〇〇が◯◯に従属するという関係を表す時に使う。

models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end

 belongs_toを使うときは単数形にする

 

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