20200808のRailsに関する記事は21件です。

ページネーション機能の実装

バージョン

・ruby 2.5.7
・Rails 5.2.4.3

ページネーション機能を使って1ページにつき10件だけ表示させたい

イメージはこんな感じ
pagination_result

まずgemをインストール

ページネーション機能の実装には、gemの「kaminari」を使います。
まず、gemfileでkaminariを記述する。
Gemfileに追記できたら、ターミナルでbundle installを実行して、kaminariをインストールします。

さらに以下のコマンドを実行

kaminariの設定ファイルを作成の為、下記を実行します。このファイル内にある数字を動かせれば1ページにつき、何件表示出来るか設定出来ます。
スクリーンショット 2020-08-08 22 31 08
下記を実行して生成されるものはkaminariのデザインの修正ができます。
詳しくはこちらの記事が分かりやすかったです。
https://qiita.com/you8/items/df68aaee3010e282d1ae
スクリーンショット 2020-08-08 22 37 13

viewとcontrollerをページネーション用に変更

kaminariを利用するには、利用したいviewファイルに以下のように追記します。
スクリーンショット 2020-08-08 22 41 41
controllerの方もallで取得していたのを以下のように修正します。
reverse_orderを指定すると新しい順に表示されるようになります。
スクリーンショット 2020-08-08 22 52 35

config/initializersフォルダのkaminari_config.rbに何件表示させたいか記入すれば完成!!

スクリーンショット 2020-08-08 22 55 08

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

参考になるRailsのプロダクトコード(OSS)5選

はじめに

「この設計でいいのかな〜」「クラス名や変数名はこれでいいのかな〜」と思ったときに参考にできそうな、Railsで書かれたOSSを5つピックアップしました。

参考になるRailsのプロダクトコード(OSS)5選

GitLab

gitlab-blog-cover.png

RubyGems.org

スクリーンショット 2020-08-08 22.23.19.png

Spree

スクリーンショット 2020-08-08 22.26.53.png

Discourse

スクリーンショット 2020-08-08 22.36.46.png

OpenProject

スクリーンショット 2020-08-08 22.40.59.png

さいごに

先人の知恵を借りて、きれいなコードを書けるように努めましょう?

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

初心者がwebpackを調べてまとめてみた

この記事について

Rails6でポートフォリオを作成していたが、webpackを理解できずに開発を進めていってたから勉強のまとめとして投稿

参考URL

https://qiita.com/kamykn/items/45fb4690ace32216ca25
https://eng-entrance.com/what-is-nodejs
https://note.com/billion_dollars/n/n596fecfdeb2e
https://qiita.com/ashketcham/items/48d64e960d436f8b6f78

webpackとは

一言でまとめるとNode.jsで、サーバーサイドで動かすモジュールバンドラーツールのこと

さて、いきなり知らん単語ばかりが並んで思考が停止しそうになるぜ。

このwebpackの説明文読んだときの頭の中を表現すると、

「Node.jsって美味しいの???」
「モジュールとは・・・」

ここは落ち着いて知らん単語を調べるのみ!!!!

Node.jsとは・・・

サーバサイドで動くJavaScriptのこと。Ruby やPHPなどと同じような働きをしてくれる。

このようにサーバサイドで動くJavaScriptをサーバサイドJavaScriptと呼ばれるが、その中でも代表的なのがNode.js。(他にも色々あるみたいだけどマイナーらしい。)Node.js = サーバサイドJavaScriptだと認識しておいてよさげ。

モジュールとは・・・

JavaScriptファイルなどのファイルのことをモジュールと呼ぶ。

ちなみに、webpackではCSSや画像もまとめてくれるので、それらもモジュールと呼ばれている!!

バンドラーとは

複数のモジュールを依存関係を解決して一つにまとめること。

そもそもバンドルとは、まとめられたファイルを意味する。

つまりここまでの用語を噛み砕いてwebpackを説明すると・・・

サーバサイドで動くJavaScriptで、CSS・JavaScript・画像などを1つのファイルとしてまとめるための便利なやつ!!!!(強引)

ここで疑問に感じるよね

なんでまとめる必要があるのか????

それはブラウザ/サーバー間での通信プロトコルであるHTTP/1.1の仕組みにある。
HTTP/1.1では一度に処理できるリクエストの数が限られている。そんな限られている中で、CSSや画像やらJSファイルを一つ一つリクエストしてると、処理速度が遅くなってしまう。
そこで、JSのリクエストをなるべく一つにまとめてリクエストの回数を減らすことが表示速度(パフォーマンス)を上げてやることができる。

ここまででなんとなくwebpackが何なのか薄らわかってきたとこで、webpackの利点をまとめる。

webpackの良いところ

其の1:外部モジュールを簡単に利用できる。

フロントエンドの開発ではjQueryやVue.jsなどを利用することが多く、webpackではこのような外部モジュールを簡単に利用することができる。

其の2:依存関係を解決したファイルを出力できる。

複数のライブラリ等を読み込んで、それに依存しているapplication.jsに読み込むファイルがあった場合は以下のようになる

qiita.rb
<!DOCTYPEhtml>
<html>
    <head>
        <metacharset="utf8"/>
        <title>dependency</title>
    </head>
    <body>
        <scriptsrc="js/extra.js"></script>
        <scriptsrc="js/jquery.js"></script>
        <scriptsrc="js/mymodule.js"></script>
        <scriptsrc="js/main.js"></script>
        <scriptsrc="js/application.js"></script>
    </body>
</html>

上記のようにしてたら、読み込みが増えれば増えるほど管理が大変になってしまtたり、読み込み順を変えると上手く動かなくなったりするかもしれないという懸念がある。

そんな懸念を解決するのがwebpackさん!!!

webpackを使わなかったらjs以下等のファイルを一つ一つ記述していたが、それらをwebpackでギュッと一つにまとめてやると、記述をかなり減らして書くことができる。

qiita.rb
<!DOCTYPEhtml>
<html>
    <head>
        <metacharset="utf8"/>
        <title>dependencyresolved</title>
    </head>
    <body>
        <scriptsrc="js/bundle.js"></script>
    </body>
</html>

他のCSSやJSの読み込みの記述は必要なく、bundle.jsにすべてまとめられている。

まとめ

webpackはフロントエンド開発用のモジュールバンドラーのこと。
モジュールバンドラとは、複数のモジュールをまとめるツールのことであり、モジュールはJavaScriptファイルなどのこと。
つまり、webpackは複数のJavaScriptファイルなどをまとめてくれるツールのことなんやな!!!

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

Secretsantasができるまで零 データベース設計 

更新予定

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

Secretsantasができるまで②  トップページ

更新予定

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

deviseで送信されるメールを非同期処理にする方法

はじめに

railsで作ったアプリケーションにdeviseを導入していますが、会員登録時などに送信するメールを非同期処理でおこないたいと思います。
メール送信処理でエラーが発生しても画面操作が続けられることがメリットです。
今回は非同期処理にdelayed_jobを使用します。
他にはside_kiqを使う方法もあります。

前提

  • deviseが使用できること。

devise-asyncのインストール

以下のgemをGemfileに追加し、bundle installしてください。

Gemfile
gem 'devise-async'

非同期処理を有効にするため、devise-async.rbを修正します。

config/initializers/devise-async.rb
Devise::Async.setup do |config|
  config.enabled = true
end

queue_adapterdelayed_jobを指定します。

config/application.rb
module [アプリ名]
  class Application < Rails::Application
    # 省略

    config.active_job.queue_adapter = :delayed_job
  end
end

Mailerをカスタマイズ

以下のようにDevise::Mailerを継承したカスタマイズクラスを作成します。

必要に応じてメソッドをオーバーライドしてください。
Devise::Mailerのメソッド

app/mailers/user_mailer.rb
class UserMailer < Devise::Mailer
  default from: 'info@example.com'

  def confirmation_instructions(record, token, opts = {})
    @token = token
    devise_mail(record, :confirmation_instructions, opts)
  end
end

devise.rbでカスタマイズしたMailerを指定します。

config/initializers/devise.rb
Devise.setup do |config|
  config.mailer = 'UserMailer'

  # 省略
end

delayed_jobのインストール

delayed_jobに関するgemをbundle installします。

Gemfile
gem 'delayed_job_active_record'

# デプロイ先でデーモンとして動かすのに必要
gem 'daemons'

delayed_jobテーブルを作成します。

rails g delayed_job:active_record
rake db:migrate

Jobをスタートさせます。

bin/delayed_job start

正常に起動できているか確認します。

$ ps aux | grep delayed_job
user 16788   0.0  2.2  4621728 183972   ??  S     2:16PM  10:24.78 delayed_job  
user 20824   0.0  0.0  4286716    720 s002  S+    7:34PM   0:00.01 grep delayed_job

Capistranoによるデプロイ

Capistrano用のgemを追加し、bundle installします。

Gemfile
gem 'capistrano3-delayed-job'

Capfileには以下のrequire文を追加してください。

Capfile
require 'capistrano/delayed-job'

参考

Ruby on Rails + Action Mailerで非同期メール送信機能の実装

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

【Rails】アプリ作成の流れ⑤【SNS認証機能】

前回は

パスワード認証機能を実装しました。

目次

  1. SNS認証機能

SNS認証機能

  • SNS認証機能はSNSに登録されているユーザー情報を取得して、自分で作ったアプリにログインさせるといった機能。
  • SNSによって取得できる情報は違う。

gem 'omniauth'を使うことで実装できる。

  • omniauthを使うことで、異なる形式を一つの統一された形式に変換してくれる。

【参考記事】
https://qiita.com/kazuooooo/items/47e7d426cbb33355590e
https://qiita.com/zenizh/items/94aec2d94a2b4e9a1d0b
https://qiita.com/Hassan/items/176bc2c6fd75a3e00111

必要なgemの導入

Gemfile
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-google-oauth2'
ターミナル
% bundle install

Omniauthに必要なカラムをUserテーブルに追加

ターミナル
% rails g migration AddOmniauthToUsers provider:string uid:string
% rails db:migrate

initializerにomniauthの設定

initializers/devise.rb
config.omniauth :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'], scope: 'email', info_fields: 'email', callback_url: "#{ENV['HOST']}/users/auth/facebook/callback"
config.omniauth :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET'], scope: 'email', oauth_callback: "#{ENV['HOST']}/users/auth/twitter/callback"
config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], scope: 'email', redirect_uri: "#{ENV['HOST']}/users/auth/google_oauth2/callback"

後々、秘密鍵をcredentials.ymlに変更する。

Userモデルの編集をする。

  • omniauthableの追記する。
  • from_omniauthを追加する。
models/user.rb
  class User < ApplicationRecord
    devise :omniauthable, omniauth_providers: %i[facebook twitter google_oauth2]
    # omniauthのコールバック時に呼ばれるメソッド
    def self.from_omniauth(auth)
      where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
        user.email = auth.info.email
        user.password = Devise.friendly_token[0,20]
      end
    end
  end

ルーティングを設定する

config/routes.rb
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }

Usersコントローラにコールバック処理を実装

  • app/controllers/users以下に
  • omniauth_callbacks_controller.rbを作成
  • そのファイルを編集
omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  # callback for facebook
  def facebook
    callback_for(:facebook)
  end

  # callback for twitter
  def twitter
    callback_for(:twitter)
  end

  # callback for google
  def google_oauth2
    callback_for(:google)
  end

  # common callback method
  def callback_for(provider)
    @user = User.from_omniauth(request.env["omniauth.auth"])
    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
    else
      session["devise.#{provider}_data"] = request.env["omniauth.auth"].except("extra")
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path
  end

end

各プラットフォーム毎に設定する

Facebook

  • Facebook APIの導入

  • Facebook for Developersの「設定」→「ベーシック」
    ここで「アプリID」と「app secret」が確認できる。

  • 「Facebookログイン」→「設定」→「有効なOAuthリダイレクトURI」
    ここでURLを保存する。
    ※まだしていない。

  • 本番で利用するときは
    プライバシーポリシーページを作成して、StatusをONにする。

Twitter

  • Twitter APIの導入 https://dev.classmethod.jp/articles/twitter-api-approved-way/#pl2
  • Twitter Developerの画面右上の「申し込む」 「Twitterユーザー向けの構築ツール
  • APIキーとシークレットキーを取得
  • callbackurlを指定
  • privacy policy
  • terms of service
  • permissionはread only
  • Request email addresses from usersにチェックを入れる

Google

  • 新しいプロジェクトを作成
  • PeopleAPI認証の有効にする
  • 認証情報を作成する→「OAuthクライアントIDの作成」
  • クライアントIDとクライアントシークレットをメモする まだしていない
  • リダイレクトURIを設定する まだしていない
  • Google+のAPIを有効にしておく

まとめ

まだ実装はできていない。
- まずはAPIの取得が大変
- APIキーとシークレットキーの管理が大変

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

【puma】puma4台インストールできない問題の対処法

環境

Rails 5.2, 6.0
puma 4.3.1, 4.3.4, 4.3.5など
xcode 12.0
xcodeのバージョンは$ xcodebuild -versionで確認できます。

「puma4台インストールできない問題」というよりは「xcode最新版を入れているとpuma4台インストールできない問題」です。
Rails6で開発をしようとしたところ、以下のようなエラーが出てpumaのインストールができませんでした。

エラー文

$ rails new new_app

$ rails new new_app
…(省略)
Fetching bootsnap 1.4.7
Installing bootsnap 1.4.7 with native extensions
Using bundler 2.1.4
Using byebug 11.1.3
Fetching regexp_parser 1.7.1
Installing regexp_parser 1.7.1
Using xpath 3.2.0
Fetching capybara 3.33.0
Installing capybara 3.33.0
Using childprocess 3.0.0
Using ffi 1.13.1
Using jbuilder 2.10.0
Using rb-fsevent 0.10.4
Using rb-inotify 0.10.1
Using listen 3.2.1
Using method_source 1.0.0
Fetching puma 4.3.5
Installing puma 4.3.5 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

current directory:
/Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5/ext/puma_http11
/Users/k_end/.rbenv/versions/2.6.6/bin/ruby -I
/Users/k_end/.rbenv/versions/2.6.6/lib/ruby/2.6.0 -r
./siteconf20200729-76891-111jdf1.rb extconf.rb
checking for BIO_read() in -lcrypto... yes
checking for SSL_CTX_new() in -lssl... yes
checking for openssl/bio.h... yes
checking for DTLS_method() in openssl/ssl.h... yes
checking for TLS_server_method() in openssl/ssl.h... yes
checking for SSL_CTX_set_min_proto_version in openssl/ssl.h... yes
creating Makefile

current directory:
/Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5/ext/puma_http11
make "DESTDIR=" clean

current directory:
/Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5/ext/puma_http11
make "DESTDIR="
compiling http11_parser.c
compiling io_buffer.c
compiling mini_ssl.c
compiling puma_http11.c
puma_http11.c:203:22: error: implicitly declaring library function 'isspace'
with type 'int (int)' [-Werror,-Wimplicit-function-declaration]
  while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
                     ^
puma_http11.c:203:22: note: include the header <ctype.h> or explicitly provide a
declaration for 'isspace'
1 error generated.
make: *** [puma_http11.o] Error 1

make failed, exit code 2

Gem files will remain installed in
/Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5 for
inspection.
Results logged to
/Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-17/2.6.0/puma-4.3.5/gem_make.out

An error occurred while installing puma (4.3.5), and Bundler cannot
continue.
Make sure that `gem install puma -v '4.3.5' --source 'https://rubygems.org/'`
succeeds before bundling.

In Gemfile:
  puma
         run  bundle binstubs bundler
Could not find gem 'rails (~> 6.0.3, >= 6.0.3.1)' in any of the gem sources
listed in your Gemfile.
         run  bundle exec spring binstub --all
Could not find gem 'rails (~> 6.0.3, >= 6.0.3.1)' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.
       rails  webpacker:install
Could not find gem 'rails (~> 6.0.3, >= 6.0.3.1)' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.

$ bundle install

$ bundle install
…(省略)
Using bundler 2.1.4
Using byebug 11.1.3
Using regexp_parser 1.7.1
Using xpath 3.2.0
Using capybara 3.33.0
Using childprocess 3.0.0
Using ffi 1.13.1
Using jbuilder 2.10.0
Using rb-fsevent 0.10.4
Using rb-inotify 0.10.1
Using listen 3.2.1
Using method_source 1.0.0
Fetching puma 4.3.5
Installing puma 4.3.5 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5/ext/puma_http11
/Users/k_end/.rbenv/versions/2.6.6/bin/ruby -I /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/2.6.0 -r ./siteconf20200729-77400-1d4l9f3.rb extconf.rb
checking for BIO_read() in -lcrypto... yes
checking for SSL_CTX_new() in -lssl... yes
checking for openssl/bio.h... yes
checking for DTLS_method() in openssl/ssl.h... yes
checking for TLS_server_method() in openssl/ssl.h... yes
checking for SSL_CTX_set_min_proto_version in openssl/ssl.h... yes
creating Makefile

current directory: /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5/ext/puma_http11
make "DESTDIR=" clean

current directory: /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5/ext/puma_http11
make "DESTDIR="
compiling http11_parser.c
compiling io_buffer.c
compiling mini_ssl.c
compiling puma_http11.c
puma_http11.c:203:22: error: implicitly declaring library function 'isspace' with type 'int (int)' [-Werror,-Wimplicit-function-declaration]
  while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
                     ^
puma_http11.c:203:22: note: include the header <ctype.h> or explicitly provide a declaration for 'isspace'
1 error generated.
make: *** [puma_http11.o] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5 for inspection.
Results logged to /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-17/2.6.0/puma-4.3.5/gem_make.out

An error occurred while installing puma (4.3.5), and Bundler cannot continue.
Make sure that `gem install puma -v '4.3.5' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  puma

$ gem install puma -v '4.3.5' --source 'https://rubygems.org/'

$ gem install puma -v '4.3.5' --source 'https://rubygems.org/'
Building native extensions. This could take a while...
ERROR:  Error installing puma:
        ERROR: Failed to build gem native extension.

    current directory: /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5/ext/puma_http11
/Users/k_end/.rbenv/versions/2.6.6/bin/ruby -I /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/2.6.0 -r ./siteconf20200729-77587-1dqkt4f.rb extconf.rb
checking for BIO_read() in -lcrypto... yes
checking for SSL_CTX_new() in -lssl... yes
checking for openssl/bio.h... yes
checking for DTLS_method() in openssl/ssl.h... yes
checking for TLS_server_method() in openssl/ssl.h... yes
checking for SSL_CTX_set_min_proto_version in openssl/ssl.h... yes
creating Makefile

current directory: /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5/ext/puma_http11
make "DESTDIR=" clean

current directory: /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5/ext/puma_http11
make "DESTDIR="
compiling http11_parser.c
compiling io_buffer.c
compiling mini_ssl.c
compiling puma_http11.c
puma_http11.c:203:22: error: implicitly declaring library function 'isspace' with type 'int (int)' [-Werror,-Wimplicit-function-declaration]
  while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
                     ^
puma_http11.c:203:22: note: include the header <ctype.h> or explicitly provide a declaration for 'isspace'
1 error generated.
make: *** [puma_http11.o] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-4.3.5 for inspection.
Results logged to /Users/k_end/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-17/2.6.0/puma-4.3.5/gem_make.out

その後、Rubyのバージョンを変えたりbrewのアップデートをしたりRubyを再インストールをしたりopensslを再インストールしたりしてみましたが、解消せず。
しかも、他の人に聞いてみたところ、同じバージョンでも普通にできている。

pumaのバージョンを3台に下げれば動くことはわかったものの、どうにか解決したい。

対処法

結論からいうと、Xcodeの最新バージョンがpumaにエラーを投げているらしい。
以下のコマンドで解消した。

$ bundle config build.puma --with-cflags="-Wno-error=implicit-function-declaration"
$ bundle install

fail to bundle install puma 4.3.5 or gem puma with ruby-2.6.6 on macos-10.15.6 - Stack Overflow

$ bundle config build.puma --with-cflags="-Wno-error=implicit-function-declaration" とは

とりあえずこれでうまく行ったけど、つまり何をやっているのさ?と思ったので、とりあえず自分の理解の範囲で調べてまとめました。

bundle config build.puma

$ bundle config
Settings are listed in order of priority. The top value will be used.
build.puma
Set for the current user (/Users/k_end/.bundle/config): "--with-cflags=-Wno-error=implicit-function-declaration"

path
Set for your local app (/Users/k_end/workspace/personal/paranoia/.bundle/config): "vendor/bundle"

pathに関しては覚えてはいないですが、$ bundle config set path 'vendor/bundle'でpathを設定していたのだと思います。

bundlerで非推奨になった --path --binstubs - Qiita
Bundlerでビルドオプションを指定する - Qiita

--with-cflags

pumaをビルドするときには、C言語で書かれたファイルをコンパイルしている(エラーではcompiling puma_http11.cでこけていた)。
--with-cflagsオプションを付けることで、puma_http11.cや他のCファイルを-Wno-error=implicit-function-declarationオプションを付けてコンパイルします。

Makefileの書き方メモ - Qiita

-Wno-error=implicit-function-declaration

-Werror-implicit-function-declarationというのが、「関数が宣言される前に使われている場合にエラーを出」すオプションらしい。そして、「これらの特定の警告オプションのそれぞれには,-Wno-で始まる警告を出力させないための否定形式があります.」ということなので、関数が宣言される前に使われている場合にエラーを出さないということです。
警告を要求/抑止するオプション - フリーソフトウェア徹底活用講座(3)

今回は、#include <ctype.h>がされていないエラーが出ているので、それをエラーとして出力しないようにしています。

結論

ということで、コンパイル時におけるエラーを発生させないようにしているだけなので、あくまでも応急処置のようなコマンドのようです。
Explicitly include ctype.h to fix compilation warning by venables · Pull Request #2314 · puma/pumaで解決しそうなので、とりあえずマージされるまでは現在のオプションを設定することでしのぎたいと思います。

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

RailsアプリをHerokuで公開する方法

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

2020年8月8日 Progate Lv.226

目標

 すでにローカルで作成済みのRailsアプリケーションをHeroku上で公開する。(すでにGitHubにコードをプッシュ済み)

方法

1.Herokuに新規登録をする

 Herokuの公式サイトから新規登録を行う。(無料)

2.Heroku用のデータベースをインストールする。

 Gemfileを以下のように書きかえる。

Gemfile
# 開発環境、テスト環境
group :development, :test do 
  gem 'sqlite3' # sqlite3
end

# 本番環境
group :production do 
  gem 'pg' # PostgreSQL
end

0808.PNG
 
 Gemfileを書きかえたら、反映するためにbundle installをする。

ターミナル
$ bundle install

データベースの設定ファイル(config/database.yml)を書きかえる。

config/database.yml
# 変更前
production:
  <<: *default
  database: db/production.sqlite3

# 変更後
production:
  <<: *default
  adapter: postgresql
  encoding: unicode
  pool: 5

0808-1.PNG

3.Herokuにログインする

ターミナル(私はGit Bashを利用しました)からログインする。

ターミナル
$ heroku login

ブラウザで以下のような画面が開く。

0808-2.PNG

 画面のLog Inを押すとログインすることができる。(ターミナルでも確認できる)

0808-3.PNG

4.Heroku上でアプリを作成する

 自分でサービス名を決めて、以下のコマンドを実行する。サービス名は、最初が文字で始まり、最後が文字または数字である必要があり、小文字、数字、ダッシュのみを含めることができる。

ターミナル
$ heroku create サービス名

 アプリが作られていれば、https://サービス名.herokuapp.com/にアクセスすると以下の画面が出てくる。

0808-4.PNG

5.Herokuにコードをプッシュする

 GitHubにコードが管理されていることが前提。

ターミナル
$ git push heroku master

 成功すると、先ほどのURLをリロードすると以下の画面が出てくる。

0808-5.PNG

ターミナル
$ heroku run rails db:migrate

 データベースを更新すると、先ほどのURLでサイトが表示される。

メモ(ファイルを変更した場合のコマンド)

ターミナル
$ git add  -A

$ git commit -m "メッセージ"

$ git push heroku master

$ heroku run rails db:migrate

データベースをリセット(初期化)する場合
$ heroku pg:reset DATABASE

問題点

以上の方法で、Heroku上Railsアプリケーションを公開することができた。
しかし、carrierwaveとcloudinaryを用いて画像を投稿する機能を作成したが、この部分がうまくいかないので、もう少し試行錯誤してみようと思う。
Herokuにcloudinaruを追加するときにクレカの登録が必要みたい、、?

参考文献

herokuのデータベースをリセット/リストア/再構築する
HerokuにRailsアプリをデプロイする手順

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

[無料で簡単]RailsアプリをHerokuで公開する方法

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

2020年8月8日 Progate Lv.226

目標

 すでにローカルで作成済みのRailsアプリケーションをHeroku上で公開する。(すでにGitHubにコードをプッシュ済み)

方法

1.Herokuに新規登録をする

 Herokuの公式サイトから新規登録を行う。(無料)

2.Heroku用のデータベースをインストールする。

 Gemfileを以下のように書きかえる。

Gemfile
# 開発環境、テスト環境
group :development, :test do 
  gem 'sqlite3' # sqlite3
end

# 本番環境
group :production do 
  gem 'pg' # PostgreSQL
end

0808.PNG
 
 Gemfileを書きかえたら、反映するためにbundle installをする。

ターミナル
$ bundle install

データベースの設定ファイル(config/database.yml)を書きかえる。

config/database.yml
# 変更前
production:
  <<: *default
  database: db/production.sqlite3

# 変更後
production:
  <<: *default
  adapter: postgresql
  encoding: unicode
  pool: 5

0808-1.PNG

2.5. herokuコマンドを使用する(herokuコマンドが使える人は飛ばしてOK)

 herokuコマンドを使うためには、heroku toolbaltというツールをインストールする必要がある。
 こちらからインストールすることができる。

 以下のコマンドでherokuコマンドが使えるか確認できる。(私はGit Bashで行った)

ターミナル
$ heroku --version

3.Herokuにログインする

ターミナル(私はGit Bashを利用した)からログインする。

ターミナル
$ heroku login

ブラウザで以下のような画面が開く。

0808-2.PNG

 画面のLog Inを押すとログインすることができる。(ターミナルでも確認できる)

0808-3.PNG

4.Heroku上でアプリを作成する

 自分でサービス名を決めて、以下のコマンドを実行する。サービス名は、最初が文字で始まり、最後が文字または数字である必要があり、小文字、数字、ダッシュのみを含めることができる。

ターミナル
$ heroku create サービス名

 アプリが作られていれば、https://サービス名.herokuapp.com/にアクセスすると以下の画面が出てくる。

0808-4.PNG

5.Herokuにコードをプッシュする

 GitHubにコードが管理されていることが前提。

ターミナル
$ git push heroku master

 成功すると、先ほどのURLをリロードすると以下の画面が出てくる。

0808-5.PNG

ターミナル
$ heroku run rails db:migrate

 データベースを更新すると、先ほどのURLでサイトが表示される。

メモ(ファイルを変更した場合のコマンド)

ターミナル
$ git add  -A

$ git commit -m "メッセージ"

$ git push heroku master

$ heroku run rails db:migrate

データベースをリセット(初期化)する場合
$ heroku pg:reset DATABASE

問題点

以上の方法で、Heroku上Railsアプリケーションを公開することができた。
しかし、carrierwaveとcloudinaryを用いて画像を投稿する機能を作成したが、この部分がうまくいかないので、もう少し試行錯誤してみようと思う。
Herokuにcloudinaruを追加するときにクレカの登録が必要みたい、、?

参考文献

挫折した人必見!HerokuでRailsアプリを公開する方法
herokuのデータベースをリセット/リストア/再構築する
HerokuにRailsアプリをデプロイする手順

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

Could9での環境開発 - チートシート (Rails Tutorial)

$ printf "install: --no-document \nupdate:  --no-document\n" >> ~/.gemrc

リスト 1.1: バージョンを指定してRailsをインストールする

$ gem install rails -v 5.1.6

リスト 1.2: Railsプロジェクトで使うenvironmentディレクトリを作成する(クラウドの場合は不要)

$ cd                  # ホームディレクトリに移動する
$ mkdir environment     # 'environment' ディレクトリを作成する
$ cd environment/       # 'environment' ディレクトリに移動する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-compose upでDBが立ち上がらない「Mysql2::Error::ConnectionError: Unknown MySQL server host 'db' (-2)」への対処法

dockerで環境構築をし、サーバーを起動しようとdocker-compose upをすると
Mysql2::Error::ConnectionError: Unknown MySQL server host 'db' (-2)というエラーが出てしまう。

ターミナルにて、エラーログを確認すると
image.png
何かが原因でDBがシャットダウンしてしまっている模様。

privilege tablesなのでsudoで作成するユーザのテーブル?
なぜdockerでこの内容のエラーが出る?
と色々疑問に思ったが、とりあえず一度全てのvolumeを削除してみることに。

解決策

①volumeのIDを調べる

$ docker volume ls
DRIVER              VOLUME NAME
local               b1d0c8467782387a61c446e1ea2aedaa745d6f120c4c6423bdc37e9005a7bd34

②上記で調べたVOLUME NAMEを用い、rmコマンドにてvolumeを削除

$ docker volume rm [VOLUME NAME]
# 例↓
$ docker volume rm b1d0c8467782387a61c446e1ea2aedaa745d6f120c4c6423bdc37e9005a7bd34

docker-compose.ymlを元に、コンテナ&サーバー再起動

$ docker-compose up --build
$ docker-compose run web rails db:create
$ docker-compose run web rails db:migrate

④localhost:3000にアクセス
無事エラーなく起動

原因

mysqlのテーブル情報が保存される場所がvolumeなのでコンテナを削除した際にvolumeコンテナを削除しきれておらず、前回のmysqlの情報が中途半端に残っていたため、権限不足となっていたみたいです。

これ以前に何回もコンテナを作って削除して、を繰り返して試行錯誤していたため、その時の残骸でしょう...

参考にした記事

docker-compose up で db が立ち上がらない時は volume を削除してみる

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

docker-compose upでDBが立ち上がらないときにやったこと

dockerで環境構築をし、サーバーを起動しようとdocker-compose upをすると
Mysql2::Error::ConnectionError: Unknown MySQL server host 'db' (-2)というエラーが出てしまう。

ターミナルにて、エラーログを確認すると
image.png
何かが原因でDBがシャットダウンしてしまっている模様。

privilege tablesなのでsudoで作成するユーザのテーブル?
なぜdockerでこの内容のエラーが出る?
と色々疑問に思ったが、とりあえず一度全てのvolumeを削除してみることに。

解決策

①volumeのIDを調べる

$ docker volume ls
DRIVER              VOLUME NAME
local               b1d0c8467782387a61c446e1ea2aedaa745d6f120c4c6423bdc37e9005a7bd34

②上記で調べたVOLUME NAMEを用い、rmコマンドにてvolumeを削除

$ docker volume rm [VOLUME NAME]
# 例↓
$ docker volume rm b1d0c8467782387a61c446e1ea2aedaa745d6f120c4c6423bdc37e9005a7bd34

docker-compose.ymlを元に、コンテナ&サーバー再起動

$ docker-compose up --build
$ docker-compose run web rails db:create
$ docker-compose run web rails db:migrate

④localhost:3000にアクセス
無事エラーなく起動

原因

mysqlのテーブル情報が保存される場所がvolumeなのでコンテナを削除した際にvolumeコンテナを削除しきれておらず、前回のmysqlの情報が中途半端に残っていたため、権限不足となっていたみたいです。

これ以前に何回もコンテナを作って削除して、を繰り返して試行錯誤していたため、その時の残骸でしょう...

参考にした記事

docker-compose up で db が立ち上がらない時は volume を削除してみる

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

form_withにselectでジャンルを選択する方法

はじめに

  • Golf Gearのreview Appを作成中
  • 投稿の際、Maker(Taylormade, calloway...)とClubの種類(Driver, Iron, Putter...)とFormから選んで投稿したい。 image.png

form_with実装

_form.html.erb
<%= form_with(model: @gear, local: true) do |form| %>
    <div class="form-group">
      <%= form.label :maker_id, "Maker" %>
      <%= form.select(:maker_id, Maker.all.map {|maker|[maker.name, maker.id] }, {prompt: "Select Maker"}, {class: "form-control"}) %>
    </div>

    <div class="form-group">
      <%= form.label :club_id, "Club"  %>
      <%= form.select(:club_id, Club.all.map {|club|[club.name, club.id] }, {prompt: "Club or Accesory"}, {class: "form-control"}) %>
    </div>

form.select(保存したいカラム、テーブルのデータをallで全て取得し、mapで一つずつ||に代入。:nameと:idに分ける

Controller編集

gears_controller.rb
class GearsController < ApplicationController
  before_action :find_gear, only: [:edit, :update]

  def new
    @gear = current_user.gears.new
  end

  def create
    @gear = current_user.gears.new(gear_params)
    if @gear.save
      redirect_to root_path, notice: 'Gear was successfully created.'
    else
      render 'new'
    end
  end

  def edit
  end

  def update
    if @gear.update(gear_params)
      redirect_to gear_path(@gear), notice: 'Gear was successfully updated.'
    else
      render 'edit'
    end
  end

  private
    def gear_params
      params.require(:gear).permit(:name, :description, :maker_id, :club_id, :image)
    end

    def find_gear
      @gear = Gear.find(params[:id])
    end

end
  • :edit, updateの際は、どの@gearかをfind(params[:id])でsetする。
  • privateメソッドで、:maker_idgear_idを引き渡しできるように設定する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerコンテナ内でrspecコマンド(自分用)

全specファイルをテスト
bin/rspec
特定のディレクトリをテスト
bin/rspec spec/system
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

lazy_high_chartsを1.6.1にアップデートしたらCIが落ちるようになった

Railsでlazy high chartsを使っていて、最近アップデートしようとしたら、エラーが出るようになって更新できなかったので、そのときに調べたことをまとめています。

気の早いお方へ

7/29以前からlazy_high_chartsを使っていて、アップデートしたら couldn't find file 'highcharts/highcharts' ...というエラーが出てあれー?ってなっている方はrake highcharts:updateをしてみてください。vendor/assets/javascripts/highcharts以下に最新のhighchartsがダウンロードされて使えるようになります。

詳細

CIでエラー

dependabotが更新のプルリクを作ってくれていたんですが、CIでエラーが出ていました。
couldn't find file 'highcharts/highcharts'と、highcharts

Failure/Error: //= require highcharts/highcharts

ActionView::Template::Error:
  couldn't find file 'highcharts/highcharts' with type 'application/javascript'
  Checked in these paths: 
    /home/circleci/repo/app/assets/config
    /home/circleci/repo/app/assets/images
    /home/circleci/repo/app/assets/javascripts
    /home/circleci/repo/app/assets/stylesheets
      :
      :

調査

地道に前回インストールしたバージョンのコミットからの差分を見ました。
大変そうですが、実は5個しかみてません。
普段から最新のgemを使うようにしていて、ホントよかったです!

それで見つけたのがこのコミットです。

内容は「gemにjsのソースをバンドルすると、本家がアップデートするたびにgemのバージョンもあげなきゃいけなくて大変だと思うので、別途利用者が個別にインストールする形をとったらどうでしょう?」というもので、最新のhighchartsを取得するrakeタスクの追加と、gem内部のjsの削除がされていました。

その後、よくよく見るとREADME.mdのインストール手順にrakeタスクの実行が追加されていました。

highchartsのインストールで解決

追加されたインストール手順のrake highcharts:updateを実行すると、無事にhighchartsをインストールできました。場所はvendor/assets/javascripts/highchartsです。

利用しているページでグラフが表示されていることを確認して、問題ないことがわかりました。

リポジトリに登録して、解決しました。

懸念事項

今までlazy_high_chartsのアップデートでhighchartsの更新が行われていたわけですが…これが自分たちでやらなくてはならなくなってしまいました…。

どうやって忘れないようにしよう…。

おわりに

sprocketsからwebpackerに移行したいです…!
そしたらアップデート作業をdependabotに頼めるので。

あとは…README.mdのblame見ればよかった。
ちゃんとREADME.mdも更新されていたので、コミットログでなくて、こちらを見たらもっと楽だったと思いました…。

image.png

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

【超初心者的】Railsアプリ作成 Part.3 ビューの設定

【超初心者的Railsアプリ作成】土台作り Part.2 コントローラの設定 の続きです。

コントローラを介してデータベースの中身をページに表示したり、データベースの中身を操作するための画面を作成します。MVCのVです。

ビューに関して

ビューは app/views/menus/ の配下に作成します。menusの部分は作成したモデル名の複数形です。コントローラに設定したメソッドと同じ名前でファイルを作成します。作成が必要なのは4つです。

メソッド名 ファイル名 使用用途
index index.html.erb 一覧表示画面
show show.html.erb 詳細表示画面
new new.html.erb 新規登録画面
edit edit.html.erb 編集画面

残り3つのメソッドはいずれかのビューで使用するため、専用のビューを必要としません。ボタンやリンクで呼び出します。

メソッド名 ファイル名 使用用途
create new.html.erb 作成
update edit.html.erb 更新
destroy いずれか 削除

ビューファイルの作成

app/views/menus/index.html.erb
<table>
  <thead>
    <tr>
      <th>商品名</th>
      <th>値段</th>
      <th>説明</th>
    </tr>
  </thead>
  <tbody>
    <% @menus.each do |menu| %>
      <tr>
        <td><%= menu.name %></td>
        <td><%= menu.price %></td>
        <td><%= menu.description %></td>
      </tr>
    <% end %>
  </tbody>
</table>
app/views/menus/show.html.erb
<table>
  <tr>
    <th>商品名</th>
    <td><%= @menu.name %></td>
  </tr>
  <tr>
    <th>値段</th>
    <td><%= @menu.price %></td>
  </tr>
  <tr>
    <th>説明</th>
    <td><%= @menu.description %></td>
  </tr>
</table>
app/views/menus/new.html.erb
<%= form_with model: @menu do |form| %>
  <div>
    <%= form.label "商品名" %>
    <%= form.text_field :name %>
  </div>
  <div>
    <%= form.label "値段" %>
    <%= form.text_field :price %>
  </div>
  <div>
    <%= form.label "説明" %>
    <%= form.text_field :description %>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end %>
app/views/menus/edit.html.erb
<%= form_with model: @menu do |form| %>
  <div>
    <%= form.label "商品名" %>
    <%= form.text_field :name %>
  </div>
  <div>
    <%= form.label "値段" %>
    <%= form.text_field :price %>
  </div>
  <div>
    <%= form.label "説明" %>
    <%= form.text_field :description %>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end

表示確認

ビューを作成したのでブラウザでアクセス出来るようになりました。URLは下記のとおりです。

メソッド名 ファイル名 使用用途 URL
index index.html.erb 一覧表示画面 http://localhost:3000/menus
show show.html.erb 詳細表示画面 http://localhost:3000/menus/1
new new.html.erb 新規登録画面 http://localhost:3000/menus/new
edit edit.html.erb 編集画面 http://localhost:3000/menus/1/edit

URLの数字(menus/1など)はデータベースに登録したデータのIDです。IDは自動で割り振られる番号で、登録した順に増えていきます。確認する場合はrails consoleを使用します。

ターミナル
% bin/rails c
ターミナル
irb(main):001:0> Menu.all
  Menu Load (0.6ms)  SELECT "menus".* FROM "menus" LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Menu id: 1, name: "ラーメン", price: 600, description: "おいしい", created_at: "2020-07-12 02:20:36", updated_at: "2020-07-12 02:20:36">

Menu id: の数字がURLの1にあたります。

レイアウトテンプレート

ところで、HTMLを少し触ったことがある人ならお気づきかもしれませんが、ビューファイルには<html><body>などのタグを書いていません。それなのにちゃんとブラウザで表示がされるのは、レイアウトテンプレートのおかげです。

ビューはコントローラの機能(メソッド)を使うための見た目を”部分的”に作成するもので、ページ全体の見た目は「app/views/layouts/application.html.erb」というファイルが担っています。このファイルには<html><body>などが書かれています。

そしてこのファイルの中に、以下の行が見つけられると思います。作成したビューはこの<%= yield %>によって読み込まれる仕組みになっています。

app/views/layouts/application.html.erb
<%= yield %>

たとえばどのページにも常に表示させたいメニューなどは、レイアウトテンプレートで見た目を作成しておけば、わざわざ1つ1つビューファイルでメニューを作る必要がありません。とても効率的ですね。

つづく

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

【初心者が悩む】RubyとPHPとPythonどれが良いのか?

まずはじめに

プログラミングをやってみようとしてまず考える事が「どのプログラミング言語から初めよう?」というところではないでしょうか。

そしてググっていくとRubyというプログラミング言語が真っ先に情報として入ってくるのではないでしょうか?

Rubyが真っ先に初心者に引っかかる理由は何と言っても「プログラミングスクールの殆どがRubyを激推ししているから」に尽きます。

ちなみに更にググってみるとRubyと似た言語にPHPとPythonがあるのです。

一体どの言語を勉強すれば良いのでしょうか?

RubyとPHPとPythonのイメージ

まず、殆ど知識が無い状態の私が持っていたイメージ。

  • Ruby - 初めて耳にする。
  • PHP - 古臭い。
  • Python - 初めて耳にする。

結果、なんとなくPHPは聞いたことがある...という程度です。私の場合は、ワードプレスでブログ運営していた時期が少しばかりあったのでPHPというプログラミング言語の存在は知っていました。

PHPは古臭いと書きましたが、ググってみると、

  • Ruby - 1993年2月24日生まれ(1995年12月発表)
  • PHP - 1994年生まれ(1995年6月8日発表)
  • Python - 1990年生まれ(1991年発表)

PHPがこの3つのプログラミング言語の中で新しいことが分かりました。イメージだけで、古臭いと決め付けていたPHPに謝りたいです。

PHPだけ少し違う!?

比較されるには何か理由がある訳ですが、この3つのプログラミング言語は、サーバーサイドでとても使われている言語です。

そして、記述の方法がとてもよく似ています。

例えばシンプルにHello world!を3つの言語で使用してみましょう!

Ruby
print "Hello World!"
PHP
<?php 
echo "Hello World!"; 
?>
Python
print("Hello world!")

......
....
..

気づきましたか!?
RubyとPythonは書き方が似ていますが、PHPだけ少しゴチャっとしていますよね。先ほどは、記述方法がよく似ていると書きましたが、実はよくよく見てみるとPHPだけどこか違います。

なぜ、PHPだけ少しゴチャっとしているのかはPHPのWikipediaに下記のような記載を見つけましたが、これが答えなのではないでしょうか?

元々PHPはプログラミング言語と言えるものではなく、単にテンプレート的な処理を行うだけであったが、度重なる機能追加やコードの書き直しにより、2017年現在リリースされているPHP 5やPHP 7は目的によらず汎用的に使うことの出来るスクリプト言語となっている。

要するに
PHPは元々、プログラミング言語とは違うものだった。
それが答えだと思います。

RubyとPHPとPythonはなぜ比較されるのか?

ここでは大きく考えられる2つの要因について書きます。

フレームワークが凄い

フレームワークは、アプリケーションを作るときに開発工程を大幅に短縮することのできるソフトウェアのことで、このフレームワークが充実している=凄い機能のアプリを短期間で作成することのできる。使える人が使えば、魔法のアイテムのようなものですが、今回紹介したRubyとPHPとPythonには、

  • Ruby - Ruby on Rails
  • PHP - Laravel
  • Python - Django

上記のような素晴らしいフレームワークが揃っています。

近年伸びているメジャーサイトが利用している

やっぱりここに尽きるのではないでしょうか?

  • Ruby - Twitter, Hulu etc...
  • PHP - Wikipedia, Facebook etc...
  • Python - Googleの検索エンジン, YouTube etc...

どのプログラミング言語も甲乙付け難い一流企業が利用しています。

結局どの言語を学べばいいのか?

興味を持った言語を学べば良いと言ったらそれで済む話ですが、

趣味ではなく就職や転職の為に学んでいるのならば単純に求人数だけを見てみると、
PHP>=Ruby>Python
の傾向なので、PHPが一番なのかもしれません。
あくまで、求人数だけでの話ですが...。

参考: 【PHP/Python/Rubyを比べてみた】言語のメリットとデメリット、特徴まとめ

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

AWS Copilotを使ってRailsアプリケーションのCI/CDパイプラインを爆速で構築する

記事の概要

AWS Copilotを使って作成したECS上のRailsアプリケーションにおいて、CI/CDパイプラインを構築します。
また、その際にハマったポイントを共有します。

↓の記事の続きになります。
AWS Copilotを使ってRailsコンテナの本番環境を爆速で構築する

AWS Copilot

AWSでコンテナ化されたアプリケーションの開発、リリースを容易に行うためのコマンドラインツールです。
コマンドを叩くとCloudFormationが動き、必要なリソースの作成やデプロイを行うことができる。CI/CDパイプラインもコマンド一つで作成できます。

※Fargate起動タイプのみサポートしています

Pipeline

Githubへのpushをトリガにしてコンテナデプロイを実行するための仕組みです。
CopilotによってPipelineを作成すると、CloudFormationによってCodeBuildのリソースが作成されます。

Pipeline作成方法

手順概要

  • GitHubの個人アクセストークン準備
  • master.keyのAmazon SSMへの登録
  • Pipelineの定義
  • Pipelineの作成
  • ビルドプロジェクトのイメージ修正(Rubyのバージョン次第)

GitHubの個人アクセストークン準備

PipelineからGithubリポジトリを参照するために、GitHubの個人アクセストークンが必要なので事前に準備しておく必要があります。

アクセストークンはrepoadmin:repo_hookのスコープが必要です。
個人アクセストークンを使用する

master.keyのAmazon SSMへの登録

Railsのmaster.keyは普通はGit管理から外すためGithub上にありませんが、PipelineはGithubリポジトリを参照してDockerイメージを作るためmaster.keyを何かしらの方法でECSに渡す必要があります。
セキュアに管理するためにAmazon SSMのパラメータストアを利用します。

下記コマンドでmaster.keyの文字列をAmazon SSMのパラメータストアに登録します。
タグの値については適宜自身のものに変更してください。

  $ aws ssm put-parameter --name RAILS_MASTER_KEY --value master.keyの文字列 --type SecureString\
  --tags Key=copilot-environment,Value=production Key=copilot-application,Value=copilot-demo

登録されました。
スクリーンショット 2020-08-05 22.53.04.png

続いて、Serviceのmanifest.ymlを変更します。
secretsを宣言することでECSからSSMのパラメータストアを参照できます。
「ECSで参照できる環境変数: SSMに登録したパラメータのキー」のように指定します。

copilot/api/manifest.yml
  variables:
    RAILS_ENV: development

  secrets:
    RAILS_MASTER_KEY: RAILS_MASTER_KEY  # ECSで参照できる環境変数: SSMに登録したパラメータのキー

Pipelineの定義

続いてPipelineを定義します。
EnvironmentやServiceを作成した時と同じように対話型で進めていきます。
このタイミングでPipelineが参照するGithubリポジトリや個人アクセストークンを指定します。

$ copilot pipeline init

  Would you like to add an environment to your pipeline? [? for help] (y/N) 
  > y

  Which environment would you like to add to your pipeline?  [Use arrows to move, type to filter, ? for more help]
  > production

  Which GitHub repository would you like to use for your service?  [Use arrows to move, type to filter, ? for more help]
  > https://github.com/ssshun/rails_copilot

  Please enter your GitHub Personal Access Token for your repository rails_copilot: [? for help] 
  > XXXXXXXXXXXXXX

完了すると以下の2ファイルが作成されます。基本はデフォルトのままで問題ないです。

  • buildspec.yml
  • pipeline.yml

Pipelineの作成

以下のコマンドでPipelineを作成

  $ git add . && git commit -m "Adding Pipeline Buildspec" && git push
  $ copilot pipeline update

しばらく経ってからCodeBuildを確認すると、ビルドプロジェクトとパイプラインが作成されているのがわかります。
スクリーンショット 2020-08-07 18.09.01.png スクリーンショット 2020-08-07 18.09.06.png

ビルドプロジェクトのイメージ修正

アプリケーションのRubyのバージョン次第ではこの手順は不要です。
2020/08/08時点では、アプリケーションがRuby2.7を利用する場合、この作業が必要でした。

buildspec.ymlを修正し、ビルドプロジェクトのrubyランタイムのバージョンを2.7に変更します。

copilot/buildspec.yml
  version: 0.2
  phases:
    install:
      runtime-versions:
        docker: 18
        ruby: 2.7

続いて、AWSコンソールにて、ビルドプロジェクト > ビルド詳細 > 環境 からイメージを修正します。
CopilotでPipelineを作成した場合、イメージがaws/codebuild/amazonlinux2-aarch64-standard:1.0になるのですが、Ruby2.7のランタイムがaws/codebuild/amazonlinux2-aarch64-standard:1.0に対応していないためです。

Ruby2.7に対応しているaws/codebuild/amazonlinux2-x86_64-standard:3.0に変更します。
スクリーンショット 2020-08-07 18.26.25.png

buildspecを修正したので、Pipelineを更新します。

  $ git add . && git commit -m "Modifying Pipeline Buildspec" && git push
  $ copilot pipeline update

以下のようにPipelineが作成されました。
スクリーンショット 2020-08-06 0.35.24.png

ハマったポイント

Pipeline名がCloudFormationの命名規則違反

プロジェクトディレクトリにアンダースコアを入れて、そのままPipelineを作成すると以下のようなエラーが発生します。

check if pipeline exists: describe stack pipeline-copilot-demo-ssshun-rails_copilot: ValidationError: 1 validation error detected: Value 'pipeline-copilot-demo-ssshun-rails_copilot' at 'stackName' failed to satisfy constraint: Member must satisfy regular expression pattern: [a-zA-Z][-a-zA-Z0-9]*|arn:[-a-zA-Z0-9:/._+]*

原因ですが、プロジェクトのディレクトリ名にアンダースコアが含まれると、デフォルトのPipeline名にもアンダースコアが入ります。
そのため、このままだとCloudFormationのスタック名の命名規則に違反してしまいます。

copilot/pipeline.yml
  # The name of the pipeline
  name: pipeline-copilot-demo-ssshun-rails_copilot # プロジェクトディレクトリがrails_copilotの場合のデフォルト

以下のようにアンダースコアが含まれないように名前を修正すれば解消されます。

copilot/pipeline.yml
  # The name of the pipeline
  name: pipeline-copilot-demo-ssshun-rails-copilot

ビルドプロジェクトのランタイムとイメージの不整合

CodeBuildにてビルドが成功しないのでログを確認したところ以下のエラーが発生していました。
アプリケーションのランタイム(Ruby2.7)とビルドプロジェクトのランタイム(Ruby2.6)の不整合が原因と思われます。

  rbenv: version `ruby-2.7.1' is not installed (set by /codebuild/output/src020247698/src/.ruby-version)

ビルドプロジェクトのランタイムをRuby2.7に変更したらいけるかと思い、buildspecを修正しましたが、

copilot/buildspec.yml
  version: 0.2
  phases:
    install:
      runtime-versions:
        docker: 18
        ruby: 2.7

再度、別のエラーが発生しました。
Ruby2.7のランタイムがCodeBuildのビルドプロジェクトのイメージaws/codebuild/amazonlinux2-aarch64-standard:1.0に対応していないためでした。

  Phase complete: DOWNLOAD_SOURCE State: FAILED
  Phase context status code: YAML_FILE_ERROR Message: Unknown runtime version named '2.7' of ruby. This 
  build image has the following versions: 2.6

上の手順で書いた通り、イメージをRuby2.7に対応しているaws/codebuild/amazonlinux2-x86_64-standard:3.0に変更して解決しました。

ECSからSSMのパラメータストアが参照不可

master.key参照のためにAmazon SSMのパラメータストアを登録する際に、タグは要らないだろうと思って以下のように登録していました。

  $ aws ssm put-parameter --name RAILS_MASTER_KEY --value master.keyの文字列 --type SecureString

その状態でPipelineでデプロイしたところ、ECSタスクで以下のエラーが発生しました。
パラメータストアのRAILS_MASTER_KEYの参照権限がないとのことでした。

  Fetching secret data from SSM Parameter Store in ap-northeast-1:AccessDeniedException:
  User: arn:aws:sts::XXXXXXXXXXXX:assumed-role/copilot-demo-production-api-ExecutionRole-DYGK7EGS469J/0e93e07304b641d58e5fc147122d2f4c is not authorized to perform: ssm:GetParameters on resource: arn:aws:ssm:ap-northeast-1:XXXXXXXXXXXX:parameter/RAILS_MASTER_KEY
  status code: 400, request id: 8b6bac83-1d83-4abe-8d19-bbaa4a5e4a29

調べていくと、manifest.ymlでsecretsを設定するとECSのタスク実行ロールにSSM参照用のIAMポリシーが作成されることがわかりました。
ポリシーをよくよく見ると、パラメータストアのタグで条件指定して参照権限を付与していました。ここら辺のきめ細やかさはすごいですね。

copilot-demo-production-apiSecretsPolicy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Condition": {
                "StringEquals": {
                    "ssm:ResourceTag/copilot-environment": "production",
                    "ssm:ResourceTag/copilot-application": "copilot-demo"
                }
            },
            "Action": [
                "ssm:GetParameters"
            ],
            "Resource": [
                "arn:aws:ssm:ap-northeast-1:XXXXXXXXXXXX:parameter/*"
            ],
            "Effect": "Allow"
        },
        {
            "Condition": {
                "StringEquals": {
                    "secretsmanager:ResourceTag/copilot-application": "copilot-demo",
                    "secretsmanager:ResourceTag/copilot-environment": "production"
                }
            },
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
                "arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXXX:secret:*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:ap-northeast-1:XXXXXXXXXXXX:key/*"
            ],
            "Effect": "Allow"
        }
    ]
}

パラメータストア登録時には以下のようにしっかりとタグ指定しましょう。

  $ aws ssm put-parameter --name RAILS_MASTER_KEY --value master.keyの文字列 --type SecureString\
  --tags Key=copilot-environment,Value=production Key=copilot-application,Value=copilot-demo

まとめ

今回作成したコードは以下です。
https://github.com/ssshun/rails-copilot

AWS Copilotを使って作成したECS上のRailsアプリケーションにおいて、CI/CDパイプラインを構築しました。
AWSコンソールでの操作は最小限で簡単にデプロイフローを作成できました。

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

『CircleCI』Rails appに、circleCIを組み込む

やりたいこと

  1. pushした際に、testを走らせsuccess or failをslackに通知。
  2. masterに、mergeされた際は1をやった後に、deploy jobを走らせsuccess or failをslackに通知

必要なこと

  1. circleCIに、登録
  2. circleCI監視下に置きたいRepositoryを設定
  3. credentials.ymlを使って環境変数を管理している場合circleCIにmaster.key用の環境変数を作る
  4. capistranoによる自動deploy flowを実現するために、circleCIに秘密鍵を登録する

master.keyの登録
see -> https://qiita.com/murata0705/items/9c99fc715d8b987a5b6e
SSH鍵の登録
see -> https://circleci.com/docs/ja/2.0/add-ssh-key/#%E6%89%8B%E9%A0%86

参考

.circleci/config.yml
version: 2.1
orbs:
  ruby: circleci/ruby@0.1.2 
  slack: circleci/slack@3.4.1
jobs:
  build:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
        environment:
          RAILS_ENV: test
          BUNDLER_VERSION: 2.1.4
      - image: circleci/mysql:5.6.47-ram
    working_directory: ~/app
    steps:
      - checkout
      - run:  
          name: Install bundler 
          command: gem install bundler -v 2.1.4 
      - run:
          name: Which bundler?
          command: bundle -v
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-
      - run:  
          name: Bundle Install  
          command: bundle install --jobs=4 --retry=3 --path vendor/bundle
      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
      - run:
          name: Wait for DB
          command: dockerize -wait tcp://127.0.0.1:3306 -timeout 120s

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      # run tests!
      - run:
          name: run tests
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"

            bundle exec rspec --format progress \
                            --format RspecJunitFormatter \
                            --out /tmp/test-results/rspec.xml \
                            --format progress \
                            $TEST_FILES
      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results
      # run rubocop
      - run:
          name: run rubocop
          command: bundle exec rubocop
      - slack/status:
          success_message: ':successcircleci:$CIRCLE_BRANCH Build Success\n:github: User:$CIRCLE_USERNAME'
          failure_message: ':failcircleci2:$CIRCLE_BRANCH Build Fail\n:github: User:$CIRCLE_USERNAME'
          webhook: '${SLACK_WEBHOOK}'
  deploy:
    docker:
      - image: circleci/ruby:2.5.1-node-browsers
        environment:
          BUNDLER_VERSION: 2.1.4
    working_directory: ~/app
    steps:
      - checkout
      - run:  
          name: Install bundler  
          command: gem update bundler 
      - run:
          name: Which bundler?
          command: bundle -v
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-
      - run:  
          name: Bundle Install  
          command: bundle install --jobs=4 --retry=3 --path vendor/bundle
      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
      - add_ssh_keys:
          fingerprints:
            - 0f:2a:6c:6a:85:f7:b4:5a:18:95:9f:71:f3:14:d0:a8
      - run:
          name: caistrano deploy
          command: bundle exec cap production deploy
      - slack/status:
          success_message: ':successcircleci:$CIRCLE_BRANCH Deploy Success\n:github: User:$CIRCLE_USERNAME'
          failure_message: ':failcircleci2:$CIRCLE_BRANCH Deploy Fail\n:github: User:$CIRCLE_USERNAME'
          webhook: '${SLACK_WEBHOOK}'
workflows:
  version: 2.1
  build-deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: master

補足

全くCI/CDに対する知識ない状態でググりながら書いたので所々おかしいです。
試しにやってみようみたいな突発的なモチベーションからだったので、、、

参考になるリンク

https://qiita.com/AK4747471/items/882b70f663f4a80ae770

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

carrierwaveを用いて画像投稿機能を作成

自分用です!!

carrierwaveをインストール

Gemfile
gem carrierwave

bundle installしてください。

uploaderを生成

ターミナル
rails g uploader board_image

これによって、「この設定をこのモデルとこのモデルに適応する」といったことを柔軟に行えるような仕様となっているのです。

uploader
def store_dir
  "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end

といった記述が施されています。

これは、carrierwaveを通じた画像のアップロード先をどこにするのかを指定しており、指定されたディレクトリに、アップロードされたファイルが保存されていくことになります。

DBに保存されるのは、ファイルそのものではなく、ファイルへの参照データになります.

モデルの関連付け

boardモデルと、boardカラムに保存される画像の関連付けを行います。

board.rb
class Board < ApplicationRecord
  belongs_to :user
  mount_uploader :board_image, BoardImageUploader
  validates :title, presence: true, length: { maximum: 255 }
  validates :body, presence: true, length: { maximum: 65_535 }
end

uploadファイルでの設定

uploard
class BoardImageUploader < CarrierWave::Uploader::Base
  storage :file

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  def default_url
    'board_placeholder.png'
  end

  def extension_whitelist
    %w[jpg jpeg gif png]
  end
end

ここで、

def default_url
'board_placeholder.png'
end

は、デフォルトでの画像を指定しています。
プレビューでは最初にここで指定した画像が表示されます。

def extension_whitelist
%w[jpg jpeg gif png]
end

は、アップロードできる拡張子を制限します。

formを作成

formをパーシャルで作成します。

ここでは、画像用のformはをfile_fieldを用います。

_form.html.erb
<%= form_with model: board, local: true, class: "form-group" do |f| %>
  <%= render "shared/error_messages", object: f.object %>
  <div class="form-group">
    <%= f.label :title %>
    <%= f.text_field :title, class: "form-control", id: "board_title" %>
  </div>  
  <div class="form-group">
    <%= f.label :body %>
    <%= f.text_area :body, class: "form-control", id: "board_body", rows: "10" %>
  </div>
  <div class="form-group">
    <%= f.label :board_image %>
    <%= f.file_field :board_image, class: "form-control", accept: 'image/*', onchange: 'previewFileWithId(preview)' %>
    <%= f.hidden_field :board_image_cache %>
    <div class='mt-3 mb-3'>
    <%= image_tag board.board_image.url, id: 'preview', size: '300x200' %>
    </div>
  </div>
  <%= f.submit (t 'defaults.create'), class: "btn btn-primary"%>
<% end %>

ここでの、accept: 'image/*は、画像ファイル全般を指定しています。

<%= image_tag board.board_image.url, id: 'preview', size: '300x200' %>

プレビューを表示しています。boardモデルのboard_imageのurlを呼び出し、表示しています。

<%= f.hidden_field :board_image_cache %>

hidden属性でimage_cacheは、画像を指定したけれども、バリデーションエラーなどにより保存が失敗した場合の画面再表示時などに、画像情報をキャッシュしておくための領域。

<%= f.file_field :board_image, class: "form-control",
 accept: 'image/*',onchange:'previewFileWithId(preview)' %>

onchangeとは、イベントハンドラープロパティで、ユーザーの入力の応じて動的に表示内容を変えるときに発生するchangeイベントを処理します。

input,select,textarea要素において、ユーザーによる要素の変更が完了した際に行われます。

onchangeの書き方は、

onchange: '関数名()'

で表します。

JSファイルを設定する

javascriptの記載は、application.jsには記載せず、別の専用ファイルを作成する。

ここでは、application.jsは個別のJavaScriptを読み込む専用のファイルのため、preview.jsファイルを作成し記載する。

preiew.js
function previewFileWithId(id) {
    const target = this.event.target;
    const file = target.files[0];
    const reader  = new FileReader();
    reader.onloadend = function () {
        preview.src = reader.result;
    }
    if (file) {
        reader.readAsDataURL(file);
    } else {
        preview.src = '';
    }
}

1行ずつ解説していくと、

const target = this.event.target;

は、

traget・・・操作を差し込む対象のオブジェクト
event.target・・・最初にイベントが起こった要素です。今回だと、ファイルを選択した時のイベントを示します。

const file = target.files[0];

targetには「files」というプロパティが用意されています。
管理されているファイルは、「File」というオブジェクトの形をしています。
このFileオブジェクトには、ファイルに関する各種の情報や、ファイルアクセスのためのメソッドなどがまとめられているのです。

files[0]で一つ目のFileオブジェクトを取り出しています。

const reader  = new FileReader();

FileReaderオブジェクトで、ファイルを取得します。

reader.onloadend = function () {
        preview.src = reader.result;
    }

onloadend、FileReaderのイベントです。データの読み込みが成功か失敗に関わらず終了した時にloadendイベントが発生し、ここに設定したコールバック関数が呼び出されます。

そして、取得されたファイルの結果を、previewのsrc属性に指定しています。

if (file) {
        reader.readAsDataURL(file);
    } else {
        preview.src = '';
    }

readAsDataURL()は、FileReaderのメソッドです。ファイルを、Data URIとして読み込むメソッドです。例えば画像ファイルをこのメソッドで読み込んで、読み込んだデータをimg要素のsrc属性に指定すればブラウザに表示できます。

前文でscr属性を指定したので、ここで、画像を表示させます。

エラーメッセージを設定

最後にエラーメッセージを設定していきます。

carrierwave.ja.ymlを新たに生成し、記載していきます。

carrierwave.ja.yml
ja:
  errors:
    messages:
      carrierwave_processing_error: 処理できませんでした
      carrierwave_integrity_error: は許可されていないファイルタイプです
      carrierwave_download_error: はダウンロードできません
      extension_whitelist_error: "は %{allowed_types}の形式でアップロードしてください"
      extension_blacklist_error: "%{extension}ファイルのアップロードは許可されていません。アップロードできないファイルタイプ: %{prohibited_types}"
      content_type_whitelist_error: "%{content_type}ファイルのアップロードは許可されていません。アップロードできるファイルタイプ: %{allowed_types}"
      content_type_blacklist_error: "%{content_type}ファイルのアップロードは許可されていません"
      rmagick_processing_error: "rmagickがファイルを処理できませんでした。画像を確認してください。エラーメッセージ: %{e}"
      mini_magick_processing_error: "MiniMagickがファイルを処理できませんでした。画像を確認してください。エラーメッセージ: %{e}"
      min_size_error: "を%{min_size}以上のサイズにしてください"
      max_size_error: "を%{max_size}以下のサイズにしてください"

これで完成です!

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