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

railsアプリ開発時にやったSEO対策メモ

概要

railsでQAサイトを作った際のSEOメモ

パンくず設置

gretelを導入。

httpsリダイレクト

config/enviroments/production.rb
config.force_ssl = true

上記でhttpsにリダイレクトされるようになる。httpとhttpsどちらにもアクセスが有るとgoogleの評価が分かれるのでリダイレクトさせる。

tag

metatagを導入して下記の対策を行う。導入方法は下記記事に記載。

https://qiita.com/you8/items/fa546663a514e7552791

devise周りのnoindex化

<% set_meta_tags noindex: true %>

deviseを導入するとuser登録周りのページが自動で作られるがこれらのページはindexされてもしょうがないのでmetatagでnoindexに。

titleのユニーク化

SEO的にはtitleはユニークなものにしたほうがいい。違うページだと認識しにくいので。これはmetatagで下記のようにviewごとに書いて出し分けるのが一番ラクそう。

<% set_meta_tags title: "タイトル1" %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby minitest キーワード引数を叩くMockの定義方法

@koshi_life です。
キーワード付き引数を叩くMockの定義方法でハマったので備忘です。

前提

  • Ruby 2.6.0
  • minitest 5.11.3

検証対象モジュール

比較用に検証対象モジュール内で通常引数、キーワード引数各Mock化する2パターンでテストを書きました。
検証対象モジュールは以下とコード参考。

  • Hoge#countup_normal_args():
    通常引数の @api_client.get , @api_client.update を叩く
  • Hoge#countup_keyword_args():
    キーワード引数の @api_client.get(item_id:),@api_client.update(item_id:, value:) を叩く
hoge.rb
class Hoge
  def initialize(api_client)
    @api_client = api_client
  end

  # countupメソッド 通常引数のモジュールを利用する
  def countup_normal_args(item_id)
    item = @api_client.get(item_id)
    new_value = item[:val] + 1
    @api_client.update(item_id, new_value)
  end

  # countupメソッド キーワード引数のモジュールを利用する
  def countup_keyword_args(item_id)
    item = @api_client.get(item_id: item_id)
    new_value = item[:val] + 1
    @api_client.update(item_id: item_id, value: new_value)
  end
end

テスト

3つのケースを定義しました。詳細はコード参照。

  • 通常引数のメソッドを叩くパターン
  • キーワード引数のメソッドを叩くパターン1
  • キーワード引数のメソッドを叩くパターン2 Proc 利用
hoge_test.rb
require 'test_helper'

class HogeTest < ActiveSupport::TestCase
  test '#Mock 通常引数のメソッドを叩くパターン' do
    item_id = 'id-1'
    retval_get = { val: 3 }
    args_get = [item_id]

    retval_update = true
    args_update = [item_id, 4]

    api_mock = MiniTest::Mock.new
    api_mock.expect(:get, retval_get, args_get)
    api_mock.expect(:update, retval_update, args_update)

    # 検証したいモジュールを実行します
    hoge = Hoge.new(api_mock)
    result = hoge.countup_normal_args(item_id)
    assert_equal(true, result)

    # Mockに送信されたデータを検証します。
    api_mock.verify
  end

  test 'キーワード引数のメソッドを叩くパターン1' do
    item_id = 'id-2'
    retval_get = { val: 3 }
    args_get = [{ item_id: item_id }] # 配列の中にハッシュで定義

    retval_update = true
    args_update = [{ item_id: item_id, value: 4 }] # 配列の中にハッシュで定義

    api_mock = MiniTest::Mock.new
    api_mock.expect(:get, retval_get, args_get)
    api_mock.expect(:update, retval_update, args_update)

    # 検証したいモジュールを実行します
    hoge = Hoge.new(api_mock)
    result = hoge.countup_keyword_args(item_id)
    assert_equal(true, result)

    # Mockに送信されたデータを検証します。
    api_mock.verify
  end

  test 'キーワード引数のメソッドを叩くパターン2 Proc利用' do
    item_id = 'id-3'
    retval_get = { val: 3 }
    retval_update = true

    api_mock = MiniTest::Mock.new
    api_mock.expect(:get, retval_get) do |item_id:|
      puts ":get() called args item_id:#{item_id}"
      item_id == 'id-3'
    end
    api_mock.expect(:update, retval_update) do |item_id:, value:|
      puts ":update() called args (item_id:#{item_id}, value:#{value})"
      item_id == 'id-3' && value == 4
    end

    # 検証したいモジュールを実行します
    hoge = Hoge.new(api_mock)
    result = hoge.countup_keyword_args(item_id)
    assert_equal(true, result)

    # Mockに送信されたデータを検証します。
    api_mock.verify
  end
end

参考

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

多対多 DBに値入らない時

real_shopテーブル----real_shop_expert_collectionテーブル(中間)----expert_collectionテーブル

それぞれテーブルを作る

real_shop.rb
  has_many :real_shop_expert_collections
  has_many :expert_collections, through: :real_shop_expert_collections
expert_collection.rb
  has_many :real_shop_expert_collections
  has_many :real_shops, through: :real_shop_expert_collections
real_shop_expert_collections.rb
  belongs_to :real_shop
  belongs_to :expert_collection

エキスパートコレクション内でリアルショップに登録してある内容をチェックボックスで全表示させたい!

expert_collections/_form.html.erb
<table class="form_area " id="is_shop_area_1">
<tr>
<th scope="row">対象店舗</th>
<td>
<% for shop in current_site.real_shops %>
<%= check_box_tag "shop_ids[]", shop.id, @expert_collection.real_shops.ids.include?(shop.id), :id => "shop_id_#{shop.id}" , :class => 'shop'%>
<%= label_tag "shop_id_#{shop.id}", shop.name %>
<% end %>
<% if current_site.real_shops.present? %>
<br />
<input type="button" value="全て選択" id="shop-true" class="set_all_button" /> <input type="button" value="全て解除" id="shop-false" class="set_all_button" />
<% end %>
</td>
</tr>
</table>

check_box_tagのリファレンス
check_box_tag(要素名 [, 値, checked = false, オプション])

スクリーンショット 2019-05-07 19.15.34.png

realshopの店舗から選択したエキスパートコレクションがshop_ids[]に格納される

なので

多対多の要素をDBに登録したい

expeert_collections_controller.rb
  def new
    @expert_collection = ExpertCollection.new
  end

  def create
    @expert_collection = ExpertCollection.new(params[:expert_collection])
    @expert_collection.site_id = current_site.id
    @expert_collection.real_shops = RealShop.find(params[:shop_ids]) if params[:shop_ids].present?
    if @expert_collection.save
      redirect_to  ar_admin_expert_collections_path, :notice => "エキスパートコレクションを作成しました。"
    else
      render :new
    end
  end

createの3行目。params[:shop_ids]があったら@expert_collection.real_shopsに格納
してからsaveすれば中間テーブルに値が入ります。

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

binding.pryで条件付きでデバッグする

what

Railsで binding.pryを使って、条件付きで処理を止める方法を知りたい。

Android Studioだとこれがデフォルトでできました。
例えば、何回もアクセスされるエンドポイントで、特定の条件のときにだけエラーが起きている場合、関係ないアクセスのときにも処理が止まってしまうとデバッグするのが面倒です。
(ある料理レシピサイトがあったとして、リストの3ページ目にアクセスしたときだけエラーが起きる場合。1ページめを表示したときにも処理が止まると面倒ですよね。)

how

まずは、 gemをインストール。

gem install pry-byebug

デバッグしたいファイルに以下を追加

require 'pry'

処理を止めたい箇所に以下を記述

binding.pry

さらに、処理を止めたい条件を以下のように記述

binding.pry if page == 3

これで page == 3のときにだけ処理が止まるようになりました。

おまけ

処理を止めた時に使えるコマンド

  • next
    • 次の行を実行(メソッドは実行される)
  • step
    • 次の行を実行、メソッドなら中に入る
  • continue
    • プログラムを実行し、pryを終了
  • break
    • break 数字=> 数字の行にbreak pointを貼る

aliasを貼る

以下を ~/.pryrcに貼り付ける

if defined?(PryByebug)
  Pry.commands.alias_command 'c', 'continue'
  Pry.commands.alias_command 's', 'step'
  Pry.commands.alias_command 'n', 'next'
  Pry.commands.alias_command 'f', 'finish'
end

直前のコマンドを Enterキーで繰り返す

以下を ~/.pryrcに貼り付ける

Pry::Commands.command /^$/, "repeat last command" do
  _pry_.run_command Pry.history.to_a.last
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

質問箱をOSS(オープンソースにしてみた)

peing-質問箱-のクローン(OSS)

ソースコードはこちら
https://github.com/seiyatakahashi/peing-questionbox-clone

クローン質問箱の現状はこんな感じです。
質問箱

質問箱とは

みなさんpeing-質問箱-をご存知ですか?twitterで2017年ごろから流行っている匿名で質問ができるwebサービスのことです。このサービスは個人開発をしているせせり氏が開発したサービスで公開1ヶ月で1億アクセスに達したそうです。

匿名で質問できるという機能はとても面白いと思いました。なのでこれをオープンソース化して、もっといろんな機能をつけていきたいと考えています。


デプロイ deploy

誰でもカンタンにデプロイできるようにしましたので是非みなさんもお使いください。
まずはgitからファイルをダウンロードしてください。

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

ダウンロードができたらダウンロードしてきたフォルダに移動する

cd peing-questionbox-clone

herokuにログインをする

heroku login

heorku でプロジェクトを作成する

heroku create 作りたいアプリ名

heorku にアップロード

git add .

git commit -m "all"

git push heroku master

アップロードしてら、データベースにマイグレード

heroku run rails db:migrate

環境変数の設定

heroku config:set APP_NAME="アプリの名前"
heroku config:set APP_NAME_EN="アプリの英語の表記名"
heroku config:set API_KEY="ツイッターのAPI KEY"
heroku config:set API_SECRET="ツイッターのAPI シークレット"
heroku config:set TOKEN="ツイッターのAccess token"
heroku config:set SECRET="ツイッターのaccess token secret"
heroku config:set CURRENT="自分ののツイッターのID"
heroku config:set DESCRIPTION="サイトの情報"
heroku config:set KEYWORDS="サイトのキーワード"
heroku config:set GOOGLE_ANALYTICS="Google アナリティクスのID"


今後つけたい機能

決済機能

質問したい人がお金を払って質問された人がお金をもらえる機能。

複数のSNSログイン

現在twitterでのログインしかできなのでfacebookやlineなどを加えたいです。

ネイティブアプリ化

railsをapi化させて、iosやandroidのようなネイティブなアプリケーションを作りたいです。

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

Rails+Docker+HerokuでCI/CD

CI/CD初心者が、CI/CDを構築した時のメモ。
初めて触ったので、誤っているところがあるかもしれません。
ご指摘いただければ、幸いです。
以下、参考にした記事等です。
https://qiita.com/kei_f_1996/items/934296e23b0d8d877ff1
https://qiita.com/Kesin11/items/47079bc7f659e71b694c
https://docs.docker.com/compose/rails/

環境

OS:mac
Ruby:2.5.0
Rails:5.2.2
CicleCI
Heroku

手順

Dockerfileの作成
docker-compose.ymlの作成
database.yml
CicleCI
Heroku

Dockerfileの作成

基本的にはdocker docsのQuickstartを参考に書きました。

Dockerfile
FROM ruby:2.5.0
RUN apt-get update -qq && apt-get install -y postgresql-client sudo
RUN curl -sL https://deb.nodesource.com/setup_11.x | sudo -E bash -
RUN sudo apt-get install -y nodejs
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

CMD ["rails", "server", "-b", "0.0.0.0"]

3行目で、nodejsのversionを指定しています。
server.pidファイルが存在する場合に、railsのサーバが再起動しなくなったので、Quickstartに書かれている、
# Add a script to be executed every time the container starts.
の部分を書いておくべきでした。

docker-compose.ymlの作成

こちらもQuickstartを基に書いてます。

docker-compose.yml
version: '3'
services:
  db:
    image: postgres:9.6
    ports:
      - '5432:5432'
    volumes:
      - postgresql-data:/var/lib/postgresql/data
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0' 
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
      - chrome
  chrome:
    image: selenium/standalone-chrome:3.141.59-dubnium
    ports:
      - 4444:4444
volumes:
  postgresql-data:
    driver: local

・自動テスト時に、chromeでのテストがうまくできていなかったみたいなので、chrome部分を追加しました。
(対処として最適かどうかはわかりません)
・コンテナを潰してもDBのデータが消えないようにするために、volumesの部分を追加。

database.yml

database.ymlに記述を追加。
これもQuickstartを参考にしました。

database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  #追加
  host: db
  username: postgres
  password:
  pool: 5

あとはbuildしたりupしたりします。

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

CicleCI

アプリケーションのルートディレクトリに、.cicleciフォルダを作ります。
.cicleci直下に以下のconfig.ymlを記述します。

config.yml
version: 2
jobs:
  build:
    machine:
      image: circleci/classic:edge
    steps:
      - checkout
      - run:
          name: docker-compose build
          command: docker-compose build
      - run:
          name: docker-compose up
          command: docker-compose up -d
      - run:
          name: sleep for waiting launch db
          command: sleep 1
      - run:
          name: "before_test: setup db"
          command: docker-compose run web rails db:create db:migrate
      - run:
          name: test
          command: docker-compose run web bundle exec rspec
      - run:
          name: docker-compose down
          command: docker-compose down
  deploy:
    machine:
      image: circleci/classic:edge
    steps:
      - checkout
      - run:
          name: "build docker image"
          command: docker build --rm=false -t registry.heroku.com/${HEROKU_APP_NAME}/web .
      - run:
          name: setup heroku command
          command: bash .circleci/setup_heroku.sh
      - run:
          name: heroku maintenance on
          command: heroku maintenance:on --app ${HEROKU_APP_NAME}
      - run:
          # HEROKU_AUTH_TOKEN is generated by `heroku auth:token`
          name: "push container to registry.heroku.com"
          command: |
            docker login --username=_ --password=$HEROKU_AUTH_TOKEN registry.heroku.com
            docker push registry.heroku.com/${HEROKU_APP_NAME}/web
            bash .circleci/heroku-container-release.sh
      - run:
          name: heroku db migrate
          command: heroku run rails db:migrate --app ${HEROKU_APP_NAME}
      - run:
          name: heroku maintenance off
          command: heroku maintenance:off --app ${HEROKU_APP_NAME}
workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: master

https://circleci.com/docs/2.0/configuration-reference/

machine: の部分でdocker-composeをそのまま使えるように設定しています。
CicleCIのEnvironmentValiablesに、
HEROKU_APP_NAME,HEROKU_AUTH_TOKEN,HEROKU_LOGIN
,HEROKU_API_KEYを設定しておきます。

デプロイ時に実行するshellは以下の二つです。

setup_heroku.sh
wget https://cli-assets.heroku.com/branches/stable/heroku-linux-amd64.tar.gz
sudo mkdir -p /usr/local/lib /usr/local/bin
sudo tar -xvzf heroku-linux-amd64.tar.gz -C /usr/local/lib
sudo ln -s /usr/local/lib/heroku/bin/heroku /usr/local/bin/heroku

cat > ~/.netrc << EOF
machine api.heroku.com
  login $HEROKU_LOGIN
  password $HEROKU_API_KEY
EOF
# machine git.heroku.com
#   login $HEROKU_LOGIN
#   password $HEROKU_API_KEY

# Add heroku.com to the list of known hosts
ssh-keyscan -H heroku.com >> ~/.ssh/known_host
heroku-container-release.sh
#!/bin/bash

imageId=$(docker inspect registry.heroku.com/${HEROKU_APP_NAME}/web:latest --format={{.Id}})
payload='{"updates":[{"type":"web","docker_image":"'"$imageId"'"}]}'
curl -n -X PATCH https://api.heroku.com/apps/${HEROKU_APP_NAME}/formation \
-d "$payload" \
-H "Content-Type: application/json" \
-H "Accept: application/vnd.heroku+json; version=3.docker-releases" \
-H "Authorization: Bearer ${HEROKU_API_KEY}"

Heroku

postgresのアドオンを入れ忘れないように気をつけて、herokuの設定も完了すれば、CICDできます。たぶん

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

Ruby2.6.x WSLでbrewを使用し、bundle installのmysqlでコケる際の対処。

tl;dr

WSLのbrewで管理しているのであれば

$ bundle config --local build.mysql2 "--with-ldflags=-L/home/linuxbrew/.linuxbrew/opt/openssl/lib"

上記コマンド後にbundle installで多分解決します。

状態

Using kaminari-core 1.1.1                                                                                                                 
Using kaminari-actionview 1.1.1                                                                                                           
Using kaminari-activerecord 1.1.1                                                                                                         
Using kaminari 1.1.1                                                                                                                      
Using launchy 2.4.3                                                                                                                       
Using ruby_dep 1.5.0                                                                                                                      
Using listen 3.1.5                                                                                                                        
Fetching mysql2 0.5.2                                                                                                                     
Installing mysql2 0.5.2 with native extensions                                                                                            
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.                                                                        

    current directory: /home/iruk/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/mysql2-0.5.2/ext/mysql2                                  
/home/iruk/.rbenv/versions/2.6.0/bin/ruby -I /home/iruk/.rbenv/versions/2.6.0/lib/ruby/2.6.0 -r ./siteconf20190507-7094-1spvp3a.rb        
extconf.rb --with-ldflags\=-L/usr/local/opt/openssl/lib                                                                                   
checking for rb_absint_size()... yes                                                                                                      
checking for rb_absint_singlebit_p()... yes                                                                                               
checking for rb_wait_for_single_fd()... yes                                                                                               
-----                                                                                                                                     
Using mysql_config at /home/linuxbrew/.linuxbrew/bin/mysql_config                                                                         
-----                                                                                                                                     
checking for mysql.h... yes                                                                                                               
checking for errmsg.h... yes                                                                                                              
checking for SSL_MODE_DISABLED in mysql.h... yes                                                                                          
checking for SSL_MODE_PREFERRED in mysql.h... yes                                                                                         
checking for SSL_MODE_REQUIRED in mysql.h... yes                                                                                          
checking for SSL_MODE_VERIFY_CA in mysql.h... yes                                                                                         
checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes                                                                                   
checking for MYSQL.net.vio in mysql.h... yes                                                                                              
checking for MYSQL.net.pvio in mysql.h... no                                                                                              
checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes                                                                              
checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes                                                                            
checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes                                                                                 
checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes                                                                                      
checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes                                                                           
checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes                                                                          
checking for my_bool in mysql.h... no                                                                                                     
-----                                                                                                                                     
Setting libpath to /home/linuxbrew/.linuxbrew/Cellar/mysql/8.0.16/lib                                                                     
-----                                                                                                                                     
creating Makefile                                                                                                                         

current directory: /home/iruk/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/mysql2-0.5.2/ext/mysql2                                      
make "DESTDIR=" clean                                                                                                                     

current directory: /home/iruk/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/mysql2-0.5.2/ext/mysql2                                      
make "DESTDIR="                                                                                                                           
compiling client.c                                                                                                                        
In file included from ./mysql2_ext.h:39:0,                                                                                                
                 from client.c:1:                                                                                                         
client.c: In function ‘rb_set_ssl_mode_option’:                                                                                           
./client.h:22:3: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]                                     
   mysql_client_wrapper *wrapper; \                                                                                                       
   ^                                                                                                                                      
client.c:127:3: note: in expansion of macro ‘GET_CLIENT’                                                                                  
   GET_CLIENT(self);                                                                                                                      
   ^~~~~~~~~~                                                                                                                             
client.c:128:3: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]                                      
   int val = NUM2INT( setting );                                                                                                          
   ^~~                                                                                                                                    
client.c:133:3: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]                                      
   int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_MODE, &val );                                                               
   ^~~                                                                                                                                    
client.c: At top level:                                                                                                                   
cc1: warning: unrecognized command line option ‘-Wno-self-assign’                                                                         
cc1: warning: unrecognized command line option ‘-Wno-parentheses-equality’                                                                
cc1: warning: unrecognized command line option ‘-Wno-constant-logical-operand’                                                            
cc1: warning: unrecognized command line option ‘-Wno-cast-function-type’                                                                  
compiling infile.c                                                                                                                        
compiling mysql2_ext.c                                                                                                                    
compiling result.c                                                                                                                        
compiling statement.c                                                                                                                     
statement.c: In function ‘rb_raise_mysql2_stmt_error’:                                                                                    
statement.c:47:3: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]                                    
   VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));                                                                
   ^~~~~                                                                                                                                  
statement.c:53:3: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]                                    
   rb_encoding *default_internal_enc = rb_default_internal_encoding();                                                                    
   ^~~~~~~~~~~                                                                                                                            
In file included from ./mysql2_ext.h:39:0,                                                                                                
                 from statement.c:1:                                                                                                      
statement.c: In function ‘rb_mysql_stmt_execute’:                                                                                         
./client.h:22:3: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]                                     
   mysql_client_wrapper *wrapper; \                                                                                                       
   ^                                                                                                                                      
statement.c:261:3: note: in expansion of macro ‘GET_CLIENT’                                                                               
   GET_CLIENT(stmt_wrapper->client);                                                                                                      
   ^~~~~~~~~~                                                                                                                             
statement.c:389:13: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]                                  
             VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);                                                                
             ^~~~~                                                                                                                        
In file included from ./mysql2_ext.h:39:0,                                                                                                
                 from statement.c:1:                                                                                                      
statement.c: In function ‘rb_mysql_stmt_fields’:                                                                                          
./client.h:22:3: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]                                     
   mysql_client_wrapper *wrapper; \                                                                                                       
   ^                                                                                                                                      
statement.c:491:3: note: in expansion of macro ‘GET_CLIENT’                                                                               
   GET_CLIENT(stmt_wrapper->client);                                                                                                      
   ^~~~~~~~~~                                                                                                                             
statement.c: At top level:                                                                                                                
cc1: warning: unrecognized command line option ‘-Wno-self-assign’                                                                         
cc1: warning: unrecognized command line option ‘-Wno-parentheses-equality’                                                                
cc1: warning: unrecognized command line option ‘-Wno-constant-logical-operand’                                                            
cc1: warning: unrecognized command line option ‘-Wno-cast-function-type’                                                                  
linking shared-object mysql2/mysql2.so                                                                                                    
/usr/bin/ld: cannot find -lssl                                                                                                            
/usr/bin/ld: cannot find -lcrypto                                                                                                         
collect2: error: ld returned 1 exit status                                                                                                
Makefile:259: recipe for target 'mysql2.so' failed                                                                                        
make: *** [mysql2.so] Error 1                                                                                                             

make failed, exit code 2                                                                                                                  

Gem files will remain installed in /home/iruk/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/mysql2-0.5.2 for inspection.                 
Results logged to /home/iruk/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/extensions/x86_64-linux/2.6.0-static/mysql2-0.5.2/gem_make.out     

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

In Gemfile:                                                                                                                               
  mysql2                                                                                                                                  

MacOS

参考 : mysql2 gemインストール時のトラブルシュート

/usr/bin/ld: cannot find -lssl

この部分が問題となっており、MacOSにおける解決の為のコマンドとしては下記コマンドになります。

$ gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/' -- --with-cppflags=-I/usr/local/opt/openssl/include --with-ldflags=-L/usr/local/opt/openssl/lib

しかし、WSLのbrewではopensllの存在するディレクトリが違うため上記コマンドでは問題の解決には至りません。

WSL

最初のエラー文を眺めると、

-----                                                                                                                                     
Setting libpath to /home/linuxbrew/.linuxbrew/Cellar/mysql/8.0.16/lib                                                                     
-----    

とあります。
WSLのbrewでは/home/linuxbrew/.linuxbrew/Cellar以下で管理をしているようですね。

 
 
では上記のbundle configを上記ディレクトリに合うように書き換えると、最初のコマンドになります。

$ bundle config --local build.mysql2 "--with-ldflags=-L/home/linuxbrew/.linuxbrew/opt/openssl/lib"

これでopensllを読み取れるようになってbundle installを正常にパスできるはず。

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

Railsのproduction環境でeager_autoload!でハマった

正確に言うとgemで使っているクラス名をModelで使ってしまい、gemが正常に読み込まれなくなってしまっただけです。

Rails5のプロジェクトをproductionで実行すると以下のエラーが発生しました。

$ bundle exec rails s -e production
vendor/bundle/ruby/2.4.0/gems/actionmailer-5.2.3/lib/action_mailer.rb:61:in `eager_load!': undefined method `eager_autoload!' for Mail:Class (NoMethodError)

まず、eager_load!って何ですか?

config/enviroments/production.rb
  # Eager load code on boot. This eager loads most of Rails and
  # your application in memory, allowing both threaded web servers
  # and those relying on copy on write to perform better.
  # Rake tasks automatically ignore this option for performance.
  config.eager_load = true

なんかパフォーマンスが良くなるらしい。
次に、RailsのActionMailerにおいてMail:Classにeager_autoload!メソッドがないと言われてしまっています。ActionMailerはMailというgemを使っているのでそれを確認しました。

mail-2.7.1/lib/mail.rb
  # This runs through the autoload list and explictly requires them for you.
  # Useful when running mail in a threaded process.
  #
  # Usage:
  #
  #   require 'mail'
  #   Mail.eager_autoload!
  def self.eager_autoload!
    @@autoloads.each { |_,path| require(path) }
  end

ありました。。。
そこでActionMailerでMailクラスを確認すると、、、

pry(ActionMailer)> Mail.superclass
=> ActiveResource::Base

なぜかActiveResourceクラスになっていました。
このプロジェクトではDB無しでActiveResourceを使っているのですが、
app/modelsを確認すると「mail.rb」がありました。。。。

gemなどで定義されているクラスに気をつけよう

言い訳をすると、
 ・config/enviroments/development.rbだとconfig.eager_loadがfalse
 ・かつ今回の開発ではメール機能は特に使っていなかった
 ・複数人で開発しておりクラス名などの設計の部分までガバナンスを効かせていなかった
といったことからデプロイ作業の時に初めてこの問題が顕在化しました。

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

Railsのdeviseで新規登録するとき、belongs_toなものも一緒に作る方法

環境

  • Rails 5.2.2
  • Ruby 2.5.3

前提

  • User belongs_to Organization
  • Organization has_many Users
  • Userを新規登録するとき、Organizationも一緒に作りたい(というか作れないとエラーで登録できない)

コード

class ApplicationController < ActionController::Base
  ...
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [organization_attributes: [:name]])
  end
end
# app/views/devise/registrations/new.html.erb
...
<% resource.organization ||= Organization.new %>
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= f.error_notification %>

  <div class="form-inputs">
    <%= f.input :email,
                required: true,
                autofocus: true ,
                input_html: { autocomplete: "email" }%>
    <%= f.input :password,
                required: true,
                hint: ("#{@minimum_password_length} characters minimum" if @minimum_password_length),
                input_html: { autocomplete: "new-password" } %>
    <%= f.input :password_confirmation,
                required: true,
                input_html: { autocomplete: "new-password" } %>

    <%= f.fields_for :organization do |organization_form| %>
      <%= organization_form.input :name %>
    <% end %>
  </div>

  <div class="form-actions">
    <%= f.button :submit, t("devise.sign_up") %>
  </div>
<% end %>
...

参考

https://github.com/plataformatec/devise#strong-parameters
https://stackoverflow.com/a/7987480/7824640

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

Railsのdeviseで新規登録するとき、親のモデル(belongs_toなもの)も一緒に作る方法

環境

  • Rails 5.2.2
  • Ruby 2.5.3

前提

  • User belongs_to Organization
  • Organization has_many Users
  • Userを新規登録するとき、Organizationも一緒に作りたい(というか作れないとエラーで登録できない)

コード

class ApplicationController < ActionController::Base
  ...
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [organization_attributes: [:name]])
  end
end
# app/views/devise/registrations/new.html.erb
...
<% resource.organization ||= Organization.new %>
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= f.error_notification %>

  <div class="form-inputs">
    <%= f.input :email,
                required: true,
                autofocus: true ,
                input_html: { autocomplete: "email" }%>
    <%= f.input :password,
                required: true,
                hint: ("#{@minimum_password_length} characters minimum" if @minimum_password_length),
                input_html: { autocomplete: "new-password" } %>
    <%= f.input :password_confirmation,
                required: true,
                input_html: { autocomplete: "new-password" } %>

    <%= f.fields_for :organization do |organization_form| %>
      <%= organization_form.input :name %>
    <% end %>
  </div>

  <div class="form-actions">
    <%= f.button :submit, t("devise.sign_up") %>
  </div>
<% end %>
...

参考

https://github.com/plataformatec/devise#strong-parameters
https://stackoverflow.com/a/7987480/7824640

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

railsのエラーをまとめてみた

railsのエラーを初心者が勉強の為にまとめてみた。

初心者が陥るエラーなんかエラー画面見たら、だいたいわかるはず。
エラー発生の理由を自分なりに言語化する為。

書き方
.表示されるエラー名
.最初に考えられる事
.エラーの理由
.エラーがでた経緯を言語化

エラーその1(NoMethodError in Tweets#index)

スクリーンショット 2019-05-07 16.51.06.png

考えられる事

undefined method `nickname' for nil:NilClassをみる。
①空のクラスにnicknameをなんて定義できないよ!って怒られる。
②cuurent.userって構文から、devise関係であり、deviseのメソッドを使っていることからコントローラー云々の記述ではない。
③current_user(ログイン状態)。ログインしている状態が空なのに、呼び出しの実行までコードが走っているのがおかしい。

原因と対策

<% if true %>

<%user_signed_in?%>と変更する。

気をつける事

deviseの知識がないと解けない問題なので、不安に感じたらdeviseとは何かを調べる必要がある。

エラーその2(ActiveRecord::RecordNotFound in UsersController#show)

スクリーンショット 2019-05-07 17.06.16.png

考えられる事

コントローラでshowアクションが見つけられないよ!
①そもそもdeviseでログイン機能を実装しているのに、見つけられないはおかしい。
②ログアウト押した時の  HTTPパラメータの渡し方を疑う。
③自分でアクション定義していないので、このようなロジックで判断。

原因と対策

<% link_to "ログアウト", destroy_user_session_path>

の記述を変更する。

<% link_to "ログアウト", destroy_user_session_path, method: :delete %>

気をつける事

こう言われる。Couldn't find User with 'id'=sign_outを聞きたい!(メンター確認

エラーその3(Routing Error)

スクリーンショット 2019-05-07 17.24.56.png

途中飛ばして

スクリーンショット 2019-05-07 17.27.52.png

考えられる事

ツイート投稿した時にルートが見つからない!
①ツイート新規投稿画面のフォームの飛ばし方を疑う。きちんとしたURLで飛んでない?
②rake routesでパスを確認する。
③対応するcreateアクションがない。おかしいと判断する。
④ルートを確認する。

原因と対策

tweetsにcreateも許可する。

Rails.application.routes.draw do
  devise_for :users
  root to: "tweets#index"
  resources :tweets, only: [:index, :show, :new, :destroy, :edit, :update, :create] do
  resources :comments, only: [:create]
  end
  resources :users, only: [:show]
end

気をつける事

ルーティングエラーはルートを見に行くのが早い??

エラーその4(ActionView::MissingTemplate in Tweets#index)

スクリーンショット 2019-05-07 17.46.07.png

考えられる事

ツイート投稿した時にルートが見つからない!
①ミッシングテンプレートとなると、部分テンプレートを怪しむ
②部分テンプレートのファイル表記確認する。
③部分テンプレートの表記方法を確認する。

原因と対策

部分テンプレート表示させていたファイル名の先頭に_を加える。

気をつける事

他に気をつける事は何かを考える、

エラーその5(SyntaxError)

スクリーンショット 2019-05-07 23.49.36.png

考えられる事

1つtweetコントローラの46行目にエンドがないよ。

原因と対策

classのエンドが抜けていた。エンドを追加

記載するまでもなし

気をつける事

SyntaxErrorは構文エラーなので、原因は把握しやすい。慌てずに。

エラーその6(NameError)

スクリーンショット 2019-05-07 23.55.30.png

考えられる事

メソッドの定義の名前あっているか確認が入っている。

原因と対策

?が抜けている

 redirect_to :action => "index" unless user_signed_in?

気をつける事

SyntaxErrorは構文エラーとなりそうだが、ならない。なぜなら、構文自体は間違っていないから。
ヘルパーメソッドの定義が違うので、作動しないだけ。注意必要。

エラーその7(処理がループしている)

スクリーンショット 2019-05-08 0.05.45.png

考えられる事

ループ処理が永遠重なっている。

原因と対策

before_action でユーザーログインしていなければ、indexアクションへredirectするように指定している。

 before_action :redirect_to_index except: [index]

 def index
   hoge
 end


 private

 def redirect_to_index
    redirect_to :action => "index" unless user_signed_in?
  end

気をつける事

SyntaxErrorは構文エラーとなりそうだが、ならない。なぜなら、構文自体は間違っていないから。
ヘルパーメソッドの定義が違うので、作動しないだけ。注意必要。

エラーその8(Routing Error)

スクリーンショット 2019-05-08 0.30.07.png

スクリーンショット 2019-05-08 0.30.18.png

考えられること

ルートエラーだからroutesをみる。

原因と対策

createのルート構築ができていない。

Rails.application.routes.draw do
  devise_for :users
  root "tweets#index"
  resources :tweets, only: [:index, :show, :new, :destroy, :edit, :update, :create] #追加
  resources :comments, only: [:create] do
  end
  resources :users, only: [:show]
end


気をつける事

routesエラーはその名の通りルーティング周りのを探す。

エラーその9()

考えられること

原因と対策

エラーその10()

考えられること

原因と対策

エラーその11()

考えられること

原因と対策

エラーその12()

考えられること

原因と対策

エラーその13()

考えられること

原因と対策

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

form_forをもう一度理解する

はじめに

railsの勉強をしていて、避けては通れないform_forというヘルパーメソッド。今回はこのなんども使うことになるform_forの使い方をもう一度おさらいしていこうと思い、この記事を着手した。

form_forとは

form_forは先述した通りrailsのヘルパーメソッドであり、モデルの編集や追加などに使われるメソッドである。つまり、特定のテーブルに対して、レコードの追加やレコードの編集などを行いたい時に、form_forを用いると簡単にその記述が書けてしまう優れものであるということ。

使い方

基本的な使い方は以下の通り。

<%= form_for(モデルクラスのインスタンス) do |f| %>
…
<% end %>

使いたいモデルのインスタンスを引数として渡すことがポイントとなる。

この時に、渡したインスタンスが情報を持っていなければcreateアクションを、インスタンスが情報を持っていたらupdateアクションを行うようになっている。この点が非常に便利となっている一つである。
メソッドの使い方はf.htmlタグ名 :カラム名と指定することで対応するformの作成をすることができる。詳しくはrailsの公式ドキュメントを参照。

form_tagとの違い

そして勉強しているときによく似たものとして出てくるのが、form_tagという存在。この違いを明確にすることで、要所でform_forを扱うことができるようになるだろう。
この二つの大きな違いは、モデルを介する操作かどうかというところによる。
例えば、入力データがモデルを持っていれば、モデルにレコードの追加編集ということでform_forを扱い、入力データがモデルを持っていなければ、単にデータを特定の要件で扱うだけということでform_tagを用いる。

form_tagはモデルにレコードとして追加しないため、検索窓、などを作成する時に用いると効果的に扱うことができる。

まとめ

今回はform_forおよびにform_tagについてまとめてみた。railsを勉強していたら必ずと言っていいほど扱うことになるため、きちんと用途を抑えて扱えるようにしておきたいところだ。

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

view_contextにしてやられた話

なんだかよく分からないレシーバとの邂逅

不具合がとあるサイトで起きてその改修作業をしていたところ、問題のコードに妙なレシーバを発見した。

view_context

これはなんなんだ?よく分からない。ファイルに対してこの文字列にgrep検索かけても定義された箇所が全く出てこない。おいおい、オメーさんよぉ、一体なんなんだよぉ〜ええ?(ミスタ風)

こいつ、独自にヘルパーメソッド作ったやつ呼ぶのに使われるってよ

独自にヘルパにーメソッドを作るには、

アプリケーション全体で使う場合は、app/helpers/application_helper.rb に書くことができる

app/views/foo/ で使う場合は、app/helpers/foo_helper.rb に書くことができる

ということで、このディレクトリパスに調べに行ったら問題のメソッドを発見することができた。

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

Rails6 のちょい足しな新機能を試す11(has_secure_password 編)

はじめに

Rails 6 に追加されそうな新機能を試す第11段。 今回のちょい足し機能は、 has_secure_password 編です。
has_secure_password にオプションとしてカラムを指定できるようになりました。

記載時点では、Rails は 6.0.0.rc1 です。 gem install rails --prerelease でインストールできます。


$  rails --version
Rails 6.0.0.rc1

プロジェクトを作成する

rails プロジェクトを作成します。

$ rails new sandbox6_0_0rc1
$ cd sandbox6_0_0rc1

Gemfile を編集して bcrypt gem を追加する

has_secure_password を使うために bcrypt gem を追加します。

Gemfile の bcrypt の行を有効にします。

Gemfile
# Use Active Model has_secure_password
gem 'bcrypt', '~> 3.1.7'

bundle を実行します

$ bundle

User モデルを作成する

User モデルを作成します。
このとき、password の他に token 用の digest カラムを追加します。

$ bin/rails g model User name password_digest token_digest
$ bin/rails db:create db:migrate

User モデルを編集する

User model を編集し、 has_secure_password を追加します。
:token オプションを追加したものとオプションを省略したもの2行を追加します。
オプションを省略した場合は従来と同じですね。

app/models/user.rb
class User < ApplicationRecord
  has_secure_password # これは今までと同じ
  has_secure_password :token, validations: true # こちらは新機能
end

has_secure_password の機能を確認する

rails コンソールから確認してみます。


$ bin/rails c
Running via Spring preloader in process 393
Loading development environment (Rails 6.0.0.rc1)

オプションなしの方は今までと変わりません。 password, password_confirmation などのメソッドが使えます。

irb(main):001:0> u = User.new
=> #<User id: nil, name: nil, password_digest: nil, token_digest: nil, created_at: nil, updated_at: nil>
irb(main):002:0> u.password
=> nil
irb(main):003:0> u.password_confirmation
=> nil

オプションを指定した :token に合わせて、 token token_confirmation などのメソッドが追加されます。

irb(main):004:0> u.token
=> nil
irb(main):005:0> u.token_confirmation
=> nil

validation を試してみると password だけではなく token についてもチェックしていることがわかります。

irb(main):006:0> u.valid?
=> false
irb(main):007:0> u.errors.full_messages
=> ["Password can't be blank", "Token can't be blank"]

tokentoken_confirmation が一致しているかどうかのチェックもしてくれます。

irb(main):008:0> u.password = 'password'
=> "password"
irb(main):009:0> u.password_confirmation = 'pass'
=> "pass"
irb(main):010:0> u.token = 'token'
=> "token"
irb(main):011:0> u.token_confirmation = 'tok'
=> "tok"
irb(main):012:0> u.valid?
=> false
irb(main):013:0> u.errors.full_messages
=> ["Password confirmation doesn't match Password", "Token confirmation doesn't match Token"]

password_confirmation, token_confirmation を設定してデータベースに保存してみましょう。

irb(main):014:0> u.password_confirmation = 'password'
=> "password"
irb(main):015:0> u.token_confirmation = 'token'
=> "token"
irb(main):016:0> u.name = 'user1'
=> "user1"
irb(main):017:0> u.save
   (0.3ms)  BEGIN
   User Create (0.5ms)  INSERT INTO "users" ("name", "password_digest", "token_digest", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["name", "user1"], ["password_digest", "$2a$10$uvvJuUHV8fNQ/0GRRtyewuboiAEUpD4Q1.0/coyWWhVxOivHnIMia"], ["token_digest", "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe27/EE56UpC"], ["created_at", "2019-05-03 23:04:03.971339"], ["updated_at", "2019-05-03 23:04:03.971339"]]
   (7.3ms)  COMMIT
=> true

今保存したデータを読み直します。


irb(main):022:0> u = User.last
User Load (0.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> #<User id: 2, name: "user1", password_digest: [FILTERED], token_digest: "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe...", created_at: "2019-05-03 23:04:03", updated_at: "2019-05-03 23:04:03">

password は今まで通り authenticate で認証できます。

irb(main):023:0> u.authenticate('pass')
=> false
irb(main):024:0> u.authenticate('password')
=> #<User id: 2, name: "user1", password_digest: [FILTERED], token_digest: "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe...", created_at: "2019-05-03 23:04:03", updated_at: "2019-05-03 23:04:03">

tokenauthenticate_token で認証できます。

irb(main):025:0> u.authenticate_token('password')
=> false
irb(main):026:0> u.authenticate_token('token')
=> #<User id: 2, name: "user1", password_digest: [FILTERED], token_digest: "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe...", created_at: "2019-05-03 23:04:03", updated_at: "2019-05-03 23:04:03">

validations: false にしたときは?

validations: false のときは、validation のチェックをしません。 authenticate_token (authenticate_xxx) は使えます。

app/models/user.rb
class User < ApplicationRecord
  has_secure_password
  has_secure_password :token, validations: false
end
irb(main):007:0> u = User.new
=> #<User id: nil, name: nil, password_digest: nil, token_digest: nil, created_at: nil, updated_at: nil>
irb(main):008:0> u.valid?
=> false
irb(main):009:0> u.errors.full_messages
=> ["Password can't be blank"]
irb(main):010:0> u.password = u.password_confirmation = 'password'
=> "password"
irb(main):011:0> u.token = 'token'
=> "token"
irb(main):012:0> u.save
(0.3ms)  BEGIN
User Create (0.7ms)  INSERT INTO "users" ("password_digest", "token_digest", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["password_digest", "$2a$10$NC1IH2S8G/RKX7myjNfeeepMknnQUuKIqD60S70iom3ZOQl92j/Cq"], ["token_digest", "$2a$10$xFYYXX7Bxk6qAOaqd3M7COlkZBCYodZK4HYQoBxLTNjr2dibMU/MK"], ["created_at", "2019-05-03 23:42:47.628292"], ["updated_at", "2019-05-03 23:42:47.628292"]]
(7.2ms)  COMMIT
=> true
irb(main):013:0> u = User.last
User Load (0.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> #<User id: 3, name: nil, password_digest: [FILTERED], token_digest: "$2a$10$xFYYXX7Bxk6qAOaqd3M7COlkZBCYodZK4HYQoBxLTNj...", created_at: "2019-05-03 23:42:47", updated_at: "2019-05-03 23:42:47">
irb(main):014:0> u.authenticate_token('pass')
=> false
irb(main):015:0> u.authenticate_token('token')
=> #<User id: 3, name: nil, password_digest [FILTERED], token_digest: "$2a$10$xFYYXX7Bxk6qAOaqd3M7COlkZBCYodZK4HYrQoBxLTNj...", created_at: "2019-05-03 23:42:47", updated_at: "2019-05-03 23:42:47">"'"]

:validations オプションを省略したときは?

validations: true を指定したときと同じ動作になります。

おまけ

irb の出力で password_digest[FILTERED] となっているのに token_digest[FILTERED] にならないのは、

config/initializers/filter_parameter_logging.rb で設定されていないからです。
token も追加してあげれば [FILTERED] になります。

config/initializers/filter_parameter_logging.rb
# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += [:password, :token]

参考情報

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

プロジェクトで使っている94つのGemを調べてみた

スクリーンショット 2019-05-06 8.26.16.png

プログラミングスクールのときは31つのGemを使っていましたが、3ヶ月前に働き出した会社のプロジェクトでは94つもありました。未だに分からないGemが多いため1つ1つ調べていくことにしました。

五月雨式で書いていくので、見づらいかと思いますがご了承ください。

94つのGem

  • rails

Ruby on Rails自体がGem

  • sass-rails

railsでsassが使えるようになる

【参考記事】Sass + Railsの基礎

  • uglifier

UglifyJS2というJavaScriptのコード軽量化ライブラリを、Rubyで簡単に使えるようになる

  • bootsnap

railsの起動時間を短縮してくれる

【参考記事】bootsnapについて調べてみた

  • puma

スピードと並列性を追求したRubyのWebサーバー

【参考記事】RackサーバーのPumaについて調べてみる

  • browserify-rails

Node.jsのモジュールシステムをブラウザでも利用できるようになる

  • jquery-ui-rails

railsにjQuery-uiを使えるようになる

  • mysql2

RubyからMySQLへ接続できるようにする

  • byebug

プログラムを途中で止めることができる。そのままコンソールを使える。

【参考記事】Ruby on Rails初心者必見。Railsでのデバッグの味方byebug(バイバグ)。

  • rails-controller-testing

コントローラーにテストを書くことができる

  • rubocop

コードスタイルを統一できる

【参考記事】cookpadのスタイルガイド

  • slim_lint

「Slim template」のLintツール

【参考記事】Slim-Lint を使ってみる

  • timecop

日付、時間のテストができるようになる

【参考記事】RailsでTimecopを使って日付/時間のテストをする

  • guard

テストを自動化する

【参考記事】Guardによるテストの自動化

  • minitest-ar-assertions/minitest-power_assert/minitest-stub_any_instance

minitestをより使いやすくするためのGem

【参考記事】minitestのオススメ基本設定調べてみた

  • better_errors

エラー画面を見やすくしてくれる。変数の中身も表示してくれる

【参考記事】Railsのデバッグが捗るエラー画面Better Errors使ってみた! 

  • binding_of_caller

メソッド呼び出し元のbindingでevalすることができる。
(よく分からなかったので、今度しっかり調べる)

【参考記事】【黒魔術】binding_of_caller gem でメソッド呼び出し元の binding を手に入れる

  • bullet

繰り返しのある記述(N+1問題)を検出してくれる

  • listen

ファイルの変更を検知してそれをフックに何か処理ができる

  • rack-dev-mark

開発環境と本番環境でのご操作を防ぐ対策になる

【参考記事】Rack_dev_markで開発環境と本番環境を視覚で判断しよう

  • rack-mini-profiler

パフォーマンスをブラウザに表示する

【参考記事】ページのロードにかかった時間が表示されます。

  • spring

アプリケーションの動作を早くする

  • spring-watcher-listen

springのファイルシステムの変更検知方法をpollingからlistenに変更してくれる

  • devise-i18n-views

【参考記事】i18n:deviseを日本語表記にする。

  • easy_translate

google翻訳が使える

  • i18n-debug

i18n で参照しているパスを調べてくれる

【参考記事】i18n-debug の基本的な使い方

  • i18n-tasks

ロケールファイルで不足している翻訳や未使用の翻訳を検出してくれる

【参考記事】i18n-tasks | Rails のロケールファイルを整理整頓する

  • i18n_generators

日本語化を助ける

【参考記事】i18n_generators で Rails アプリを日本語化

  • brakeman

開発中の静的セキュリティチェック

  • bundler-audit

gemのセキュリティ情報のチェック

  • html2slim

erbファイルをSlimに一括変換する

  • license_finder

パッケージのライセンスを出力する

【参考記事】プロジェクトで使用しているパッケージのライセンスを出力する

  • rails_best_practices

railsアプリのコードの質をチェックするメトリクスツール

【参考記事】Ruby on Railsのコードをよりモダンにするために、Rails Best Practices入門・おすすめ設定

  • stackprof

プログラムが実行された際、どの部分のコードの実行にどれだけの時間がかかったのかを計測してくれる

【参考記事】StackProfを使ってrubyプログラムのプロファイリングをする方法

  • swagger_ui_engine

Swagger-UIをMountable Engineとして使える。
(よく分からなかったので、今度しっかり調べる)

【参考記事】Swagger+JSON SchemaでAPIの型をテストして開発サイクルをスピードアップさせた話

  • daemons

rubyスクリプトをデーモン化し、外部からコントロール(start, stop等)できるようにする。

【参考記事】BestGems Pickup! 第6回

  • delayed_job/delayed_job_active_record

バックグラウンドで非同期処理をしてくれる

【参考記事】delayed_jobの機能についてもうちょっと

【参考記事】RailsでAcitiveJobとDelayedJobを使ってバックグランド処理を行う

  • delayed_job_web

遅延ジョブを監視する

  • letter_opener_web

送信メールがWebインタフェースで確認できる

【参考記事】【Rails】letter_opener_webを利用してメールを確認する

  • rails_db

GUIでDB内容が見れる

  • activerecord-session_store

データベースにセッションを保管する

  • devise

ログイン機能の実装を簡単にしてくれる

【参考記事】【Rails】deviseを導入してみる

  • devise-encryptable

(よく分からなかったので、今度しっかり調べる)

【参考記事】Rails4: 古いdeviseのパスワードを新しいdeviseで使う方法

  • pundit

認可まわりの機能を作成する

【参考記事】Punditを使って権限を管理する

  • grape/grape-entity/grape-swagger/grape-swagger-entity

Restful な WEB-API が簡単に作成できる

【参考記事】Rails5でGrapeを使って簡単にAPIを実装する

  • aasm

状態遷移の機能を簡単に付けることができる機能

【参考記事】aasm を使って,Ruby のクラスに状態遷移の機能を付与する

  • activerecord-import

まとめてINSERTできる。

【参考記事】(初心者向け)activerecord-importの使い方

  • activerecord-userstamp

ユーザがモデルに対して作成、更新、削除した情報を持たせることができる

  • acts_as_list

モデルの並び順の維持や、並びの変更を行う

  • by_star

日付分析関連の便利scope群

  • draper

第4のレイヤを提供するGem

【参考記事】Draperで驚くほどRailsコードがわかりや少なったよ!

  • enum_help

enumをI18n化する

【参考記事】【Rails】enumをI18n対応させるenum_helpが便利すぎた

  • paper_trail/paper_trail-association_tracking

監査、またはバージョン管理のためにモデルの変更を追跡するツール

  • paranoia

論理削除を実装できる

  • validates_timeliness

動く日付時刻用のvalidates

  • cells/cells-rails/cells-slim

ビューの一部を切り離し、Rubyの通常のクラスとしてコンポーネント化する

【参考記事】Railsコードを改善する7つの素敵なGem

  • cocoon

1対多で関連するデータを1つのフォーム画面に記述できる

【参考記事】nested_form はもう古い!? Cocoon で作る1対多のフォーム

  • data-confirm-modal

削除確認ダイアログを出してくれる

Data-Confirm Modalを使って Rails の削除確認ダイアログをいい感じにする

  • font-awesome-rails

FontAwesomeのアイコンを使えるようになる

【参考記事】Font Awesome Lcons

  • icalendar

icalendarがrailsで使えるようになる

  • kaminari

ページネーション機能が使える

railsチュートリアルで使われているgem「will_paginate」と同じような用途

【参考記事】railsチュートリアル 9章

  • ransack

検索機能

【参考記事】Ransackのススメ

  • redcarpet

マークダウンを実装できる

【参考記事】Railsのプロジェクトにgem 'redcarpet' を用いてマークダウンエディタを導入してみた

  • simple_form

formの記述を簡単な形に、少なくできる

【参考記事】Railsのform_for内のコードをすっきりさせるsimple_formの使い方

  • slim/slim-rails

HTMLのViewをSlim形式で書くことができる。コード量が少ない

【参考記事】Slim入門をして書き方を練習をしました

  • config

開発環境ごとに定数を YAML ファイルで管理できる

【参考記事】Railsで定数を環境ごとに管理するrails_config(現 config)

  • dotenv-rails

カレントディレクトリに .env という名前で環境変数を書き込んでおくと、自動的にENVの中にその値を追加してくれる

【参考記事】環境によって変わる設定値はdotenvを使うと便利

  • seedbank

seeds.rbを分割し、ファイルの肥大化を防いでくれる

  • business_time

Dateに対していろいろなmethodが使えるようになる。

【参考記事】[ruby] 祝日判断や休日判断をrubyでやるなら、これでいいんじゃないかな - メモ

  • geoip

特定のホストまたはIPアドレスを検索し、そのIPアドレスが割り当てられている国などの情報を返してくれる

  • gon

controller内でセットした変数をJavascript内で使う事ができる。

  • hashie

Hashをより良くしてくれる

【参考記事】Hash をよりパワフルにする hashie gem を試す #ruby

  • responders

respond_withとrespond_toが使える

【参考記事】respond_withとクラスレベルのrespond_toの扱いについて

  • whenever

cronのバッチを作成して、定期的に回すことができる。

【参考記事】Railsで定期的にバッチ回す「Whenever」

  • aws-sdk-rails

s3に画像ファイルをフォームからアップロードできる

【参考記事】railsのaws-sdk gemを使ってs3に画像ファイルをフォームからアップロードする

  • ridgepole

スキーマ管理用のコマンドラインツール

【参考記事】Rails マイグレーションの廃止とRidgepoleの導入・利用方法

  • aws-sdk

S3を使えるようになる

  • rack-attack

同一IPからの過剰なアクセスを制限できる

  • sentry-raven

エラーログ収集に使うツール

【参考記事】RailsアプリケーションにSentryを導入する

  • whiny_validation

Validation Errorをログ出力

  • derailed_benchmarks

RailsアプリやRubyアプリのさまざまなベンチマークを取ることができる。

【参考記事】Rails: 多機能ベンチマークgem「derailed_benchmarks」README(翻訳)

  • memory_profiler

Railsアプリで文字列ががっつり重複を取り除いてくれる

【参考記事】Rails: memory_profilerでRuby文字列の重複を削減する(翻訳)

最後に一言

何か間違っている点があれば、教えて頂けたら幸いです。
よろしくお願い致します。

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

【10日間でポートフォリオ作成に挑戦】10日目:AWSでのデプロイ

概要

今回は、2019年のGW期間(10日間)を全て費やして取り組むポートフォリオの製作過程を取りまとめた内容を投稿させて頂きます。(投稿は毎日行う予定)

全体通した取り組みの詳細については、前回までの記事をご参照ください。

【10日間でポートフォリオ作成に挑戦】1日目:要件定義〜記事投稿のCRUD
【10日間でポートフォリオ作成に挑戦】2日目:アクセス制限〜コメントのCRUD機能
【10日間でポートフォリオ作成に挑戦】3日目:ページネーション~CKEditorの導入
【10日間でポートフォリオ作成に挑戦】4日目:テーブル分割〜CKEditorのフォームへの反映
【10日間でポートフォリオ作成に挑戦】5日目:CKEditorへ画像アップロード機能を追加
【10日間でポートフォリオ作成に挑戦】6日目:テストコードの実装
【10日間でポートフォリオ作成に挑戦】7日目:検索機能〜いいね機能の実装
【10日間でポートフォリオ作成に挑戦】8日目:記事ストック機能〜ユーザーフォロー機能の実装
【10日間でポートフォリオ作成に挑戦】9日目:フロントエンドの実装〜各種機能の修正

今日一日の作業内容

ここからは、今日1日で取り組んだ作業内容をご説明します。

本番環境へのデプロイ

一通りの機能の実装が完了したので、本番環境へのデプロイを行います。
デプロイにあたっては、AWSを利用しています。

下記の様な構成です

  • 仮想サーバー:EC2(AWS)
  • ストレージ:S3(AWS)
  • アプリケーションサーバー:Unicorn
  • WEBサーバー:Nginx
  • データベース:MySQL
  • 自動デプロイ:Capistrano

これでデプロイ出来たものが、下記のURLです。
http://3.112.115.114/

今日の失敗

ここから、今日の失敗をまとめます。

画像アップロードが本番環境で動作しない

開発環境では正常に動作していた画像アップロードですが、本番環境ではエラーが発生してしまい、正常に動作しませんでした。

1dd4028d8017a68f6039ef37304a2044.gif

アクセスキーの設定ミス

エラーログを確認して見ると、下記の様な内容でした。

rake stderr: rake aborted!
NoMethodError: undefined method `match' for nil:NilClass

色々調べて見ると、どうやらAWSのアクセスキーの読み込みが上手く行っていなかった様でした。

これまで、secrets.ymlで読み込ませる方法しか実践した事が無かったのですが、今回Rails5.2を利用する事にした為、secrets.ymlcredentials.ymlに置き換わっていた事を失念していました。

なので、credentials.ymlからアクセスキーを読み込ませる様に設定を変更しました。
なお、credentials.ymlは直接エディタで編集を行えない為、ターミナルから下記コマンドを実行して、編集を行う必要があります。

$ EDITOR="vi" bin/rails credentials:edit

また、復号化に必要なmaster.keyは、デフォルトで.gitignoreに追加されているため、Capistranoの自動デプロイに関する設定ファイルに読み込ませる為の記述を追加する必要があります。

config/deploy.rb
set :linked_files, %w{ config/master.key }

(中略)

  desc 'upload master.key'
  task :upload do
    on roles(:app) do |host|
      if test "[ ! -d #{shared_path}/config ]"
        execute "mkdir -p #{shared_path}/config"
      end
      upload!('config/master.key', "#{shared_path}/config/master.key")
    end
  end
  before :starting, 'deploy:upload'
  after :finishing, 'deploy:cleanup'
end

私は、前に実装したコードをそのまま引用した為に、master.keyと指定すべき箇所を、secrets.ymlと指定してエラーになってしまいました。

Shrineの設定

上記のアクセスキーの設定を行った後も、別のエラーが発生しました。

Shrine::Error (storage :cache isn't registered on ImageUploader):

公式のリファレンスも確認して、コードを書き換えたりしたのですが、これが一向に解消されません。

uploaders/image_uploader.rb
require 'image_processing/mini_magick'

class ImageUploader < Shrine
  plugin :remove_attachment
  plugin :pretty_location
  plugin :processing
  plugin :versions
  plugin :delete_raw
  plugin :store_dimensions, analyzer: :mini_magick

  process(:store) do |io, _|
    versions = { original: io }

    io.download do |original|
      pipeline = ImageProcessing::MiniMagick.source(original)
      versions[:standard] = pipeline.resize_to_limit!(400, 400)
    end

    versions
  end
end
config/initializers/shrine.rb
require 'shrine/storage/s3'

if Rails.env.production?
  s3_options = {
      access_key_id: Rails.application.credentials.dig(:aws, :access_key_id),
      secret_access_key: Rails.application.credentials.dig(:aws, :secret_access_key),
      region:            'ap-northeast-1',
      bucket:            'gooderorrs',
  }

  Shrine.storages = {
      cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
      store: Shrine::Storage::S3.new(**s3_options),
  }
else
  require 'shrine/storage/file_system'

  Shrine.storages = {
      cache: Shrine::Storage::FileSystem.new('public', prefix: 'uploads/cache'),
      store: Shrine::Storage::FileSystem.new('public', prefix: 'uploads')
  }
end

Shrine.plugin :activerecord
Shrine.plugin :backgrounding
Shrine.plugin :logging
Shrine.plugin :determine_mime_type
Shrine.plugin :cached_attachment_data
Shrine.plugin :restore_cached_data

これについては、今日中の解決が困難だった為、一旦持ち越しにしています。

今後の予定

結局、当初予定していた機能を期間内で全て実装する事が出来ませんでした・・・
その辺りの改善点などは、また時間が取れた時にまとめて投稿しようと思います。

また、これで終了ではなく、機能拡張は続けて行くので、それもある程度まとまった内容が書ける段階で記事に出来ればと考えています。

おまけ

最後になりますが、現在、私は下記の目標を立てて学習に取り組んでいます。

  • 3年間で「10,000時間」をプログラミングに費やす
  • その間、毎日ブログの投稿を行う

Twitterでは、その過程で学んだ事などを発信しています。
もし宜しければフォローしてみてください。

Twitter:@ryoutaku_jo
ブログ:りょうたくのWEBエンジニア日記

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