20210113のRubyに関する記事は25件です。

Railsで内容を更新しても反映されない?

★内容を更新したのに反映されない。。。

springが悪さをしているかも?

springはrails sをした時に
同時に立ち上がり読み込むもので古い情報を読み込んでいる可能性がある

❶rails sをして別タブでspring stop

再度springをして挙動を確認すると悪さをなくせるかも?

$ spring stop

Spring is not running

❶プロセスを見てみる

ここで下記以外のプロセスがあるようならKILL

ps aux | grep spring

12.png

$ ps aux | grep spring で、KILLを実行後、KILLできているか確認

$ kill -9 ◯◯◯◯

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

Rubyでスプレッドシートからデータを取得するAPIを作成してみた(with サービスアカウント)

概要

スプレッドシートからデータを取得してjsonに整形するみたいなAPIを作成してみました。
意外とサービスアカウントを使っている記事が少なかったので少しでも参考になればと思います。

環境

Ruby: 2.5.7
Ruby on Jets: 2.3.18
(Railsライクなフレームワークなので、Railsでもできます)

サービスアカウントについて

ユーザではなくアプリケーションレベルで使用するアカウント。ユーザで認証する場合はそのユーザがアプリケーションの管理からはずれた場合などに認証情報を変えなければならなくなるため不便。

サービスアカウントはユーザに依存しないのでユーザなど関係なくアプリケーションからAPIを叩く場合にはこちらが便利。

ちなみに今回はバックエンドからユーザなど関係なくAPIを叩くために使うのでサービスアカウントを利用しました。

サービスアカウントの作成

作成していない場合は以下を参考に作成してください。ここでは省きます

https://support.google.com/a/answer/7378726?hl=ja

サービスアカウントからcredentialファイルをjsonファイルで取得

認証情報 > サービスアカウントを管理 > 作成したアカウントの操作 > 鍵を作成

image.png

json形式で鍵を作成して、
ダウンロードしたらアプリのトップディレクトリにgoogle-sa-credential.jsonという名前で保存

APIを有効にする

https://console.developers.google.com/apis/dashboard

GCPコンソールの「APIとサービス」から

  • Google Drive API
  • Google Sheets API

を検索してそれぞれ有効にする。

サービスアカウントでフォルダ、スプレッドシートを作成する

サービスアカウントでスプレッドシートを操作する際には、以下のいずれかの必要がある。

  • サービスアカウントによって作成されている
  • サービスアカウントが編集者として共有されている
  • 公開されている

(gem google driveの記述)

スプレッドシートの「共有」でサービスアカウントを許可できるならば、共有した時点でこのタスクを必要としないので飛ばしてもOKです。

以下に2つのrakeタスクを記述しました

  • 指定したフォルダID配下にフォルダを作成する(指定しなければ最上階にフォルダを作成する)
  • 指定したフォルダID配下にスプレッドシートを作成する
# lib/tasks/google_drive.rake

namespace :google_drive do
  desc 'Create folder with Service account'
  task :create_folder, [:title, :email_address, :collection_id] => :environment do |_, args|
    session = create_session
    folder = if args[:collection_id]
               session.file_by_id(args[:collection_id]).create_subcollection(args[:title])
             else
               session.root_collection.create_subcollection(args[:title])
             end

    folder.acl.push(type: 'user', email_address: args[:email_address], role: 'writer')
    p "Created folder: #{folder.human_url}"
  end

  desc 'Create sheet with Service account'
  task :create_sheet, [:title, :email_address, :collection_id] => :environment do |_, args|    
    session = create_session
    sheet = session.file_by_id(args[:collection_id]).create_spreadsheet(args[:title])
    sheet.acl.push(type: 'user', email_address: args[:email_address], role: 'writer')
    p "Created sheet: #{sheet.human_url}"
  end

  def create_session
    ::GoogleDrive::Session.from_service_account_key('google-sa-credential.json')
  end
end

$ bundle exec rake 'google_drive:create_folder[<フォルダタイトル>,<共有したいユーザのメールアドレス>]'
$ bundle exec rake 'google_drive:create_sheet[<シートタイトル>,<共有したいユーザのメールアドレス>]'

補足

collection: folderの意味

ACL: アクセス制御リスト。IAMと似て非なるもの。バケットやオブジェクトに柔軟に権限を与えるっぽい。

その後の実装方針

  1. gem google_driveを使用する
    • spreadsheet → worksheet → json
  2. gem google_api_clientを使用する
    • 柔軟性がない(jsonで出力できてもどちらにせよしたい形に変換しなければならない)

json構造を柔軟に変更できるように1の方針で実装しました。

所々moduleに切り分けていますが必須ではないです
また、routingは省略します

Gemfile

gem 'google_drive'

controller

# controller

session = GoogleDriveSession.create_session

service = SpreadSheetToHashService.new(session)
service.run!

render json: JSON.dump(service.records)

session作成module

# google_drive_session.rb

module GoogleDriveSession
  CREDENTIAL_PATH = 'google-sa-credential.json'

  def self.create_session
    return ::GoogleDrive::Session.from_service_account_key(CREDENTIAL_PATH)
  end
end

ちなみに自分の場合は開発環境以外ではSecrets Managerから取得する(gem 'aws-sdk-secretsmanager'を使用)

# google_drive_session.rb

module GoogleDriveSession
  CREDENTIAL_PATH = 'google-sa-credential.json'

  def self.create_session
    return ::GoogleDrive::Session.from_service_account_key(CREDENTIAL_PATH) if Jets.env.development?

    credential_json = RequestSecretsManager.request('/<project_name>/google-sa-credential')
    credential_hash = JSON.parse(credential_json)
    File.open("/tmp/#{CREDENTIAL_PATH}", 'w') do |f|
      JSON.dump(credential_hash, f)
    end
    ::GoogleDrive::Session.from_service_account_key("/tmp/#{CREDENTIAL_PATH}")
  end
end

# request_secrets_manager.rb
require 'aws-sdk-secretsmanager'

module RequestSecretsManager
  def self.request(secret_name)
    client = Aws::SecretsManager::Client.new(region: Jets.aws.region)
    get_secret_value_response = client.get_secret_value(secret_id: secret_name)
    get_secret_value_response.secret_string
  end
end

service

# spread_sheet_to_hash_service.rb

class SpreadSheetToHashService
  attr_reader :records

  SPREADSHEET_ID = '<スプレッドシートのID>'
  WORKSHEET_ID = '<ワークシートのID(1ページ目は0)>'
  HEADER_COUNT = 1

  def initialize(session)
    @session = session
  end

  def run!
    worksheet = SpreadSheet.identify_worksheet_by_id(@session, SPREADSHEET_ID, WORKSHEET_ID)
    convert_worksheet_to_hash(worksheet)
  end

  private

  def convert_worksheet_to_hash(worksheet)
    # ヘッダをスキップする
    @records = worksheet.rows(HEADER_COUNT).map { |row|
      # ここでセル単位で好きな形にする
      # rowは行のこと、一行ずつのイテレータ
      # row[0]: 1列目の値
      # row[1]: 2列目の値
    }
  end
end

spread sheet操作用module

# spread_sheet.rb

module SpreadSheet
  def self.identify_worksheet_by_id(session, spread_sheet_id, work_sheet_id)
    spreadsheet = session.file_by_id(spread_sheet_id)
    spreadsheet.worksheet_by_sheet_id(work_sheet_id)
  end
end

まとめ

これでスプレッドシートの内容を柔軟にjsonへ変更してレスポンスとして返すことができます!

参考

gem google-drive

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

Railsでtext_areaに入力した文章を改行込で表示させる方法

はじめに

Ruby on Railsにて投稿機能のあるアプリを作成しました。
投稿画面にはRailsのヘルパーメソッドであるform_withメソッドを用いてフォームを実装。その際、text_areaで、複数行の入力が可能な入力欄を作成したので、改行を含めた文章を入力し、それを表示させようとしたところ、改行がされていない文章が表示されてしまいました。
今回はその問題を解決する方法を調べたので記事に残しておきます。

環境

macOS Catalina バージョン 10.15.7
Ruby 2.6.5
Ruby on Rails 6.0.3.4

前提として

フォームのコードは以下の通り。

app/views/shared/_form.html.erb
<%= form_with(model: word, local: true) do |f| %>
  <%= f.text_area :remarks, placeholder: "備考", rows: "7" %>
  <%= f.submit "Register" %>
<% end %>

念の為、このフォームで送られてくるデータに改行が含まれているか、Railsのデバックツールであるpry-railsを用いて確認してみました。
まず、以下の改行を含めた文章をフォームで送信しました。

おはよう
こんにちは

ここで、コントローラーに記述したbinding.pryのところで一旦処理が止まるので、コンソールでparamsを実行し、送られてきたパラメーターを確認したところ、
Image from Gyazo
このように、改行した部分に改行コードである「\r\n」が含まれており、ちゃんとデータとして送られてきていることが分かります。

そして、送られてきたデータを表示するためのコードが以下なのですが、

app/views/show.html.erb
<%= @word.remarks %>

このままだと改行が含まれず、以下のような表示になってしまいます。

おはよう こんにちは

この問題を解決する方法を、以下2通りまとめました!

①simple_formatメソッド

以下のサイトを参考にしました。

simple_formatはRailsのヘルパーメソッドで、下記の機能を有します。

  • 文字列を<p>で括る
  • 改行は<br>を付与
  • 連続した改行は</p><p>を付与

では、show.html.erbにsimple_formatを追記します。

app/views/show.html.erb
<%= simple_format(@word.remarks) %>

そして、ブラウザを更新し改めて確認してみると、

おはよう
こんにちは

このように改行がされたまま表示がされました!

ChromeのデベロッパーツールのElementsパネルを確認すると、
Image from Gyazo
たしかに、文字列がpタグで括られており、改行にはbrタグが付与されています。

そこで、試しに以下のようにいくつか連続した改行の場合を調べてみました。

おはよう



こんにちは

しかし、表示を確かめると、

おはよう
こんにちは

このように、連続した改行は実際に入力した通りには表示されませんでした。
ちなみに、Elementsパネルを確認すると、以下のようにpタグが付与されていました。
Image from Gyazo
なので、この場合は改行ではなく、「おはよう」と「こんにちは」それぞれが1つの段落として表示されている事になります。

②safe_joinメソッド

以下のサイトを参考にしました。

safe_joinメソッドもRailsのヘルパーメソッドの一つです。
simple_formatと違い、pタグで括らず、また連続した改行を表示させることができます。

まず、先ほどの記述を以下のように書き換え。

app/views/show.html.erb
<%= safe_join(@word.remarks.split("\n"),tag(:br)) %>

※ splitは文字列を分割するメソッド。引数に区切り文字を指定することで、その区切り文字のところで文字列を区切る。

あとは、同じように複数の連続した改行を含む文章を入力してみると、

おはよう


こんにちは

きちんと入力した通りの表示がされました!!
Elementsパネルを確認すると、
Image from Gyazo
brタグで改行されているのが分かります。

最後に

色々調べる中で、HTMLやタグ、エスケープ処理などの記事を目にしました。これらの理解が深まったと同時に、知らない知識に触れる機会も多かった為、さらに派生させ知識をより深めていきたいと思います。

誤った箇所などありましたら、ご指摘いただけると幸いです。

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

text_areaに入力した文章を改行込で表示させる方法

はじめに

Ruby on Railsにて投稿機能のあるアプリを作成しました。
投稿画面にはRailsのヘルパーメソッドであるform_withメソッドを用いてフォームを実装。その際、text_areaタグで、複数行の入力が可能な入力欄を作成したので、改行を含めた文章を入力し、それを表示させようとしたところ、改行がされていない文章が表示されてしまいました。
今回はその問題を解決する方法を調べたので記事に残しておきます。

環境

macOS Catalina バージョン 10.15.7
Ruby 2.6.5
Ruby on Rails 6.0.3.4

前提として

フォームのコードは以下の通り。

app/views/shared/_form.html.erb
<%= form_with(model: word, local: true) do |f| %>
  <%= f.text_area :remarks, placeholder: "備考", rows: "7" %>
  <%= f.submit "Register" %>
<% end %>

念の為、このフォームで送られてくるデータに改行が含まれているか、binding.pryを用いて確認してみました。
まず、以下の改行を含めた文章をフォームで送信しました。

おはよう
こんにちは

ここでbinding.pryを用いて、コンソールで「params」を実行し、パラメーターを確認したところ、
Image from Gyazo
このように、改行した部分に改行コードである「\r\n」が含まれており、ちゃんとデータとして送られてきていることが分かります。

そして、送られてきたデータを表示するためのコードが以下なのですが、

app/views/show.html.erb
<%= @word.remarks %>

このままだと改行が含まれず、以下のような表示になってしまいます。

おはよう こんにちは

この問題を解決する方法を、以下2通りまとめました!

①simple_formatメソッド

以下のサイトを参考にしました。

simple_formatはRailsのヘルパーメソッドで、下記の機能を有します。

  • 文字列を<p>で括る
  • 改行は<br>を付与
  • 連続した改行は</p><p>を付与

では、show.html.erbにsimple_formatを追記します。

app/views/show.html.erb
<%= simple_format(@word.remarks) %>

そして、ブラウザで更新し改めて確認してみると、

おはよう
こんにちは

このように改行がされたまま表示がされました!

ChromeのデベロッパーツールのElementsパネルを確認すると、
Image from Gyazo
たしかに、文字列がpタグで括られており、改行にはbrタグが付与されています。

そこで、試しに以下のようにいくつか連続した改行の場合を調べてみました。

おはよう



こんにちは

しかし、表示を確かめると、

おはよう
こんにちは

このように、連続した改行は実際に入力した通りには表示されませんでした。
ちなみに、Elementsパネルを確認すると、以下のようにpタグが付与されていました。
Image from Gyazo
なので、この場合は改行ではなく、「おはよう」と「こんにちは」それぞれが1つの段落として表示されている事になります。

②safe_joinメソッド

以下のサイトを参考にしました。

safe_joinメソッドもRailsのヘルパーメソッドの一つです。
simple_formatと違い、pタグで括らず、また連続した改行を表示させることができます。

まず、先ほどの記述を以下のように書き換え。

app/views/show.html.erb
<%= safe_join(@word.remarks.split("\n"),tag(:br)) %>

※ splitは文字列を分割するメソッド。引数に区切り文字を指定することで、その区切り文字のところで文字列を区切る。

あとは、同じように複数の連続した改行を含む文章を入力してみると、

おはよう


こんにちは

きちんと入力した通りの表示がされました!!
Elementsパネルを確認すると、
Image from Gyazo
brタグで改行されているのが分かります。

最後に

色々調べる中で、HTMLやタグ、エスケープ処理などの記事を目にしました。これらの理解が深まったと同時に、知らない知識に触れる機会も多かった為、さらに派生させ、知識をより深めていきたいと思います。

誤った箇所などありましたら、ご指摘いただけると幸いです。

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

Rails の Carrierwave を使用した画像複数アップロード機能

備忘録のため記述しています。

Carrierwave という gem の導入

carrierwaveuploader/carrierwave

Gemfile に以下を記述

gem "carrierwave", "~> 2.0"

終わったらターミナルにて bi

bundle install

Uploader の作成

bundle exec rails g uploader Images

下記が作成される。

images_uploader.rb

アップロードされるファイルの設定を色々弄れるけど今回は見送り。

MVC 設定

Post モデルに対して Memory というモデルで画像登録をできるようにしている

Model

post.rb

post.rb
class Post < ApplicationRecord
  has_many :memories
  accepts_nested_attributes_for :memories, allow_destroy: true
  validates :title, presence: true
end

accepts_nested_attributes_for :memories の設定で posts_controller 上で memories を登録できるようにしている。

memory.rb

class Memory < ApplicationRecord
  belongs_to :post
  mount_uploader :image, ImagesUploader
end

mount_uploader は carrierwave の設定。これで簡単にアップロードできるようになる。

Controller

posts_controller.rb

class PostsController < ApplicationController


  def new
    @post = Post.new
        # @post.memories.build とすることで post に紐づいた memories を保存する準備が整う
    @post_memory = @post.memories.build
  end

  def create
    post = Post.new(post_params)
    if post.save!
        # 以下は memories に保存する処理。 each 文で複数の画像を保存可能。
      params[:memories][:image].each do |image|
        post.memories.create(image: image, post_id: post.id)
      end
    end
    redirect_to root_path
  end

  private

    def post_params
      params.require(:post).permit(:title, memories_attributes: [:image]).merge(user_id: current_user.id)
    end
end

View

new.html.erb

<%= form_for @post, local: true,  html: {class: "form_area"} do |f| %>
        <div class="form_area__field">
          <%= f.text_area :title, id: "post_text", placeholder: "投稿内容を入力", rows: 10%>

          <div class="form_area__image_field">
            <%= f.fields_for :memories do |m| %>
              <%= m.label :image, "画像" %>
              <%= m.file_field :image, multiple: true, name: "memories[image][]" %>
              <%= hidden_field :memories, :post_id, value: @post.id %>
            <% end %>
          </div>

          <div class="form_area__hidden_field">
            <%= hidden_field :post, :user_id, value: current_user.id %>
          </div>

          <div class="form_area__action">
            <%= f.submit "投稿", class: "form_area__action__btn" %>
          </div>

        </div>
      <% end %>

fields_for で1回の投稿で複数のクラスを保存できる。

multiple: true で複数の画像を投稿できるように設定している。

表示するには?

<% @post.memories.each do |m|%>
     <%= image_tag m.image.url %>
<% end %>

上記で表示できる。

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

"Hello World"でみるプログラミング言語

はじめに

プログラミングを学ぶものなら誰しも通る道、Hello world。今回はそんなHello worldを出力するためのプログラムで、5つのプログラミング言語を比較していきます。皆さんが使っていなそうな言語を選びました。が、あくまでも独断と偏見によって決定されています。

1.C

いわずと知れた言語ですね。

#include <stdio.h>
int main(void) {
    printf("Hello world\n");
}

2.Python

簡潔に書けることが利点ですね。

print("Hello world")

3.Ruby

日本生まれの言語ですね。

puts "Hello world"

4.FORTRAN

最初の高水準言語として有名ですね。まだ息してたのか...

program hello
  print *, 'Hello world'
end program hello

5. PHP

HTML/CSSと一緒に使われたりしますね。

<?php
  echo "Hello world\n";
?>

終わりに

以上です。いかがでしたでしょうか?Rubyはやっぱり短かったですね。それが長所ですよね。PHPechoてエコーって読むんですね。初めて知りました。若輩者ゆえ、もしかしたら間違いがあるかもしれないので、その時はコメントなんかで教えてください。

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

Ruby:正規表現まとめ※コードサンプルあり

初心者向けに主な正規表現のパターンを整理しました。

主な正規表現のパターン一覧

パターン 意味
[a-z] 角括弧で囲まれた文字のいずれか1個にマッチ(この場合はaからzのいずれかの文字にマッチ)
\d 数字にマッチ
\D 数字以外にマッチ
{n} 直前のパターンを n 回繰り返すものにマッチ
{n, m} 直前の文字が少なくともn回、多くてもm回出現するものにマッチ
. 改行以外のどの1文字にもマッチ
* 直前の部分式の0回以上の繰り返しにマッチ
+ 直前の部分式の1回以上の繰り返しにマッチ
\A 直後の文字列が先頭にある文字列にマッチ
\z 直前の文字列が末尾にある文字列にマッチ
?= 直後に設定した文字が続く文字列にマッチ
*? 直前に設定した文字が0回以上続く文字列をチェックし、?の直後の文字が出た段階でその1文字を返す

修飾子

パターン 意味
/i 大文字と小文字を区別しない

「*」の意味を深ぼる

パターン 意味
* 直前の文字の0回以上の繰り返しにマッチ

「*」の「0回以上の繰り返しにマッチ」がちょっとわかりにくいので、「+」と比較しながら確認します。

パターン 意味 マッチする文字列の例
a.*z aから始まってzで終わる2桁以上の文字列 az, abz,abczなど
a.+z aから始まってzで終わる3桁以上の文字列 abz,abczなど

「*」自体は1桁以上の文字列を求めないので、上記の例のように2桁の文字列でもマッチします。「.」は改行以外のどの文字にもマッチしますが、「*」が後に付くことで「.」にあたる文字がなくてもマッチします。
一方で「+」は1桁以上の文字列を求めるので、2桁の文字列にはマッチしません。

そのまま使える正規表現の主なパターン一覧

パターン 意味
/\A[ぁ-んァ-ン一-龥]/ 全角ひらがな、全角カタカナ、漢字
/\A[ァ-ヶー-]+\z/ 全角カタカナ
/\A[a-zA-Z0-9]+\z/ 半角英数
/\A\d{3}[-]\d{4}\z/ 郵便番号(「-」を含む且つ7桁)
greater_than_or_equal_to: 〇〇 〇〇以上の数値
less_than_or_equal_to: 〇〇 〇〇以下の数値

参考資料

Ruby 3.0.0 リファレンスマニュアル>正規表現
murashun.jp 基本的な正規表現一覧

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

Rails must be exist系のエラー(@saveが実行されない)

結論

belongs_toにoptional: trueを追加。

belongs_to :post, optional: true

なぜか?

Rails5から、デフォルトで紐づけているテーブル同士の、どちらかにデータが無いままDBに格納しようとすると、エラーになる。だから、どちらにデータがない状態のままでもokに」するために'optional: true'を書く必要がある。

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

Mysql2::Error: Incorrect string valueのエラーの対処

はじめに

データベースを作成してマイグレーションも実行し、「いざ、データ(日本語)を入れて動かそう!」としたところで、以下のようなエラーが。。。

Mysql2::Error: Incorrect string value: '\xE3\x82\xB2\xE3\x82\xB9...' for column 'name' at row 1

どうやら文字コードが不適切らしい。。。
ということで、本記事では、このエラーについて、僕が試したことや解決策を書いていこうと思います!

技術・環境

Docker/docker-compose
ruby 2.7.2
rails 6.0.2.3

試したこと

テーブルの文字コードを調べてみました。

まずは、MySQLに接続する。

$ docker-compose run web rails db

次に、データベースの文字コードを調べてみる。

> show variables like '%char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | latin1                     |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

やはり、データベースの文字コードが「latin1」だった。。。
※MySQLはdefaultで「latin1」が文字コードに設定されています。

データベースの文字コードをutf8mb4に修正
僕はDockerを使用していたので、以下のようにdocker-compose.ymlを修正。
※以下の公式ドキュメントを参考にしました。
https://hub.docker.com/_/mysql

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.7
    # この下の一行を追記しました
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_USER: root
    ports:
      - "3306:3306"
    volumes:
      - ./db/mysql/volumes:/var/lib/mysql

  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/share-read
    ports:
      - "3000:3000"
    depends_on:
      - db
volumes:
  mysql-data:
    driver: local

データベースの文字コードを再度調べてみる。

> show variables like '%char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

「よし!修正完了!」と思ったが、まだエラーが出てしまう。
もしかしたらテーブル自体の文字コードが「latin1」のまま?と思い調べてみると。。。

> SHOW CREATE TABLE users;

| users | CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  # 中略
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |

やはり、テーブルの文字コードが変わっていなかった。。。

テーブルの文字コードをutf8mb4に修正
ということで、テーブルの文字コードを修正するのですが、10個くらいテーブルがあって一つ一つ修正するのは大変!
また、まだテーブルにデータを格納してなかったこともあり、今回はマイグレーションをやり直そうことにしました。

$ docker-compose run web rails db:reset
$ docker-compose run web rails db:migrate

ということで無事解決しました。
もし同じようなエラーが出てしまった方は、参考にしてみてください。

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

Cognito と API Gateway と Lambda と Dynamo

はじめに

前回のものにユーザ認証を足すために Cognito を Authorizer として使う。出来上がりはこんな感じ。User は Cognito で認証されて、APIをコールできるようになったり、自分でサインナップできるようになったりする。

image.png

だいたいこの記事の通りでよい

プログラミングせずにCognitoで新規ユーザー登録&サインインを試してみる というわかりやすい記事があるので、この通り実行するのがよい

Cognito の多くのサンプルは WebUI を書くところから始まるものだが、この記事はひとまず AWS CLI を使って、アプリコードなしに基本的な設定方法が理解できるのがよい

やってみて、記事との微妙な差分は下の画面の Cognito > ユーザープール > アプリクライアント > 認証フローの設定 のところが以下のように少し変わっているくらい。認証フローの設定は以下で動きました

image.png

あと、一番最初にユーザープールを作るところで「クライアントシークレットを作成」にデフォルトで入っているチェックを外すべし。

image.png

外さないと CLI でユーザーをサインアップする時に以下のエラーになった

An error occurred (NotAuthorizedException) when calling the SignUp operation: Unable to verify secret hash for client <CLIENT_ID>

テストしてみる

先のページの通り、$TOKEN にトークンを入れておいて

curl -H "Authorization: $TOKEN"  https://****.execute-api.us-east-2.amazonaws.com/dev_1/projects | jq
curl -X POST -d '{"project-id": 200, "name": "Typing", "is-default": false}' -H "Authorization: $TOKEN"  https://****.execute-api.us-east-2.amazonaws.com/dev_1/projects | jq
curl -X DELETE -d '{"project-id": 200}' -H "Authorization: $TOKEN"  https://****.execute-api.us-east-2.amazonaws.com/dev_1/projects | jq
curl -X PUT -d '{"project-id": 200, "name": "Yoga"}' -H "Authorization: $TOKEN"  https://****.execute-api.us-east-2.amazonaws.com/dev_1/projects | jq

次回予告

CLI では動いたので、いよいよ次回はフロントエンドのクライアントをかいてみようか

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

belongs_to, has_many 関連付けの :foreign_keyオプションのデフォルト設定

  • belongs_toの:foreign_keyオプション

Railsのデフォルトでは、このモデルの外部キーを保持するカラム名は、関連付け名に_idを付けたものと想定している。
belongs_to.png

  • has_manyの:foreign_keyオプション

Railsのデフォルトでは、(関連の)相手方モデルの外部キーを保持するカラム名は、こちらのモデル名に_idを付けたものと想定している。
has_many.png

参考:Rails Guides Active Record Association

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

ビット操作メソッド三題(Ruby)

正統的なビット操作の話ではありません。

Ruby を使っているとビット操作はあまり必要なのだが、競技プログラミングをやっていたりすると時にはビット操作したくなることもある。で、Ruby をあらためて見てみると(当り前だが)ビット操作も一通り揃っているわけだ。競プロで便利だなと思うのは例えば「ビットの立っている」ものを数える、つまり整数を二進数表現に直して "1" の数を数えるというもので、Ruby では

to_s(2).chars.count { _1 == "1" }

と簡潔に書ける。
 また、いわゆる「ビット全探索」は、Ruby ではビットを使わなくても簡単に書ける。「ビット全探索」でよく使われるのが部分集合の列挙で、例えば、[0, 1, 2, 3, 4]のすべての部分集合を列挙する場合は、

ary = [0, 1, 2, 3, 4]
co = 0
(0..ary.size).each do |i|
  ary.combination(i) do |sub_set|
    p [co, sub_set]
    co += 1
  end
end

などと書ける。結果は

[0, []]
[1, [0]]
[2, [1]]
[3, [2]]
[4, [3]]
[5, [4]]
[6, [0, 1]]
[7, [0, 2]]
[8, [0, 3]]
[9, [0, 4]]
[10, [1, 2]]
[11, [1, 3]]
[12, [1, 4]]
[13, [2, 3]]
[14, [2, 4]]
[15, [3, 4]]
[16, [0, 1, 2]]
[17, [0, 1, 3]]
[18, [0, 1, 4]]
[19, [0, 2, 3]]
[20, [0, 2, 4]]
[21, [0, 3, 4]]
[22, [1, 2, 3]]
[23, [1, 2, 4]]
[24, [1, 3, 4]]
[25, [2, 3, 4]]
[26, [0, 1, 2, 3]]
[27, [0, 1, 2, 4]]
[28, [0, 1, 3, 4]]
[29, [0, 2, 3, 4]]
[30, [1, 2, 3, 4]]
[31, [0, 1, 2, 3, 4]]

となって、確かにすべての部分集合が列挙されている(空集合を含む場合)。Ruby の組み込みメソッドにないのが不思議なくらいだが、これはだから

class Array
  def each_subset(empty_set=true)
    if block_given?
      n = empty_set ? 0 : 1
      (n..size).each do |i|
        combination(i) { yield _1 }
      end
    else
      to_enum(__method__, empty_set)
    end
  end
end

とでもして Array クラスに追加してやったらよい。で、上のと同じ結果を得ようと思えば

[0, 1, 2, 3, 4].each_subset.with_index do |ary, i|
  p [i, ary]
end

とでもすればよいのである。これがあったら競プロに便利だろうね。組み込みに追加されないかね。

本題っぽいもの

で、以下が本題というほどでもないのだが、Ruby にビット操作メソッドを三つ追加してみた。いずれも実用性はないものと考えられ、たんなる遊び。
こんな感じである。

class Integer
  def each_bit
    if block_given?
      to_s(2).chars.reverse.map { _1 == "1" }.each { yield _1 }
    else
      to_enum(__method__)
    end
  end

  def bit_reverse
    to_s(2).chars.reverse.join.to_i(2)
  end
end

class Array
  def to_i
    map { _1 ? "1" : "0" }.reverse.join.to_i(2)
  end
end

まず、Integer#each_bitはビットごとにブロックを実行し、ブロック変数にはビットが 1 ならtrue、0 ならfalseが入るというもの。

0b10101101.each_bit { p _1 }

を実行すると

true
false
true
true
false
true
false
true

が出力となる。下位ビットから実行されていくのに注意。だから最後は必ずtrueになる。

173.each_bit.map(&:itself)    #=>[true, false, true, true, false, true, false, true]
0.each_bit.map(&:itself)      #=>[false]

173.to_s(2)    #=>"10101101"

など。
 

Array#to_iはこれの反対。配列の中身で真のものを 1、偽のものを 0 として二進表現と見做し、Integer に変換する。これも配列の左から下位ビットに詰められていくのに注意。

[true, false, true, true, false, true, false, true].to_i    #=>173
[:a, nil, 1, -1, 1 == 0, 1.integer?, 0.1 < 0, true].to_i    #=>173
173.each_bit.map(&:itself).to_i    #=>173

 

Integer#bit_reverseはテキトーに思いついたもの。Integer のビットを左右反転させた Integer を作る。まぎらわしいが、ビット反転(否定、Integer#~)とは全然ちがうのです。

173.bit_reverse    #=>181
173.to_s(2)    #=>"10101101"
181.to_s(2)    #=>"10110101"

なお、bit_reverse.bit_reverseは必ずしも元に戻らないので注意(わかりますね?)。
 

おそまつ様でした。

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

belongs_to関連付けのforeign_keyオプション(Rails Guide自分用訳)

Railsの慣例では、相手のモデルを指す外部キーを保持しているjoinテーブル上のカラム名については、そのモデル名にサフィックス_idを追加した関連付け名が使われることを前提とします。:foreign_keyオプションを使えば、外部キーの名前を直接指定できます。


By convention, Rails assumes that the column used to hold the foreign key on this model is the name of the association with the suffix _id added. The :foreign_key option lets you set the name of the foreign key directly:


  • 自分用訳

Railsの慣例では、このモデルの外部キーを保持するカラム名は、関連付け名に接尾辞の_idを付けたものと、想定している。:foreign_keyオプションを使えば、外部キーを保持するカラム名を指定することができる。


Railsが慣例で想定しているforeign_key

class Book < ApplicationRecord
  belongs_to :author(, foreign_key: author_id)
end

関連付け名に_idをつけたforeign_keyをRailsは想定しているので、下の例では :foreignkeyオプションが必要となる。

class Book < ApplicationRecord
  belongs_to :author, class_name: "Patron",
                        foreign_key: "patron_id"
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

belongs_to関連付けのforeign_keyオプション(Rails Guide自分用訳)

Railsの慣例では、相手のモデルを指す外部キーを保持しているjoinテーブル上のカラム名については、そのモデル名にサフィックスidを追加した関連付け名が使われることを前提とします。:foreignkeyオプションを使えば、外部キーの名前を直接指定できます。


By convention, Rails assumes that the column used to hold the foreign key on this model is the name of the association with the suffix _id added. The :foreign_key option lets you set the name of the foreign key directly:


  • 自分用訳

Railsの慣例では、このモデルの外部キーを保持するカラム名は、関連付け名に接尾辞の_idを付けたものと、想定している。:foreign_keyオプションを使えば、外部キーを保持するカラム名を指定することができる。

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

【環境構築】localからrailsのアンインストール

はじめに

railsを誤ってlocalにインストールしてしまったので、アンインストールした時の手順を記事にしとこうと思います。

環境

macOS Catalina バージョン 10.15.7
Homebrew 2.7.1
rbenv 1.1.2
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin19]
gem 3.2.4
Bundler version 2.1.4

手順

先ずcdでホームディレクトリに移動します。
続いてgem listでgemをインストール状況を確認します。

gem list

*** LOCAL GEMS ***

.
.
.
rails (6.1.0, 6.0.3.4, 6.0.0)
rails-dom-testing (2.0.3)
rails-html-sanitizer (1.3.0)
rails-i18n (6.0.0)
railties (6.1.0, 6.0.3.4, 6.0.0)
rake (13.0.3, 13.0.1, 12.3.2)
.
.
.

rails (6.1.0, 6.0.3.4, 6.0.0)
rails6.0.0のみ使用するので、他の2つをアンイストールします。

アンインストール

gem uninstall railsだけでいいのかと思いきや、調べるとrailtiesも削除する必要があるらしい。

gem uninstall rails -v '6.0.3.4'
gem uninstall railties -v '6.0.3.4'

上のコマンドを打つと、下記が表示される。

You have requested to uninstall the gem:
        railties-6.0.3.4

rails-6.0.3.4 depends on railties (= 6.0.3.4)
If you remove this gem, these dependencies will not be met.
Continue with Uninstall? [yN]  y
Successfully uninstalled railties-6.0.3.4

「railsアンインストールしてもいい?」と聞かられてるので、yを入力します。
[yN]は[yes or No]という意味です。

続いて6.1.0をアンインストールします。

gem uninstall rails -v '6.1.0'
gem uninstall railties -v '6.1.0'  

You have requested to uninstall the gem:
        railties-6.1.0

rails-6.1.0 depends on railties (= 6.1.0)
If you remove this gem, these dependencies will not be met.
Continue with Uninstall? [yN]  y

アンインストールできてるか確認します。

rails -v                           
Rails 6.0.0

できてました!!

最後、bundle inatallで読み込んで、変更を反映します。

bundle inatall

これで完了です。

最後に

同じ様に悩んでる方々の助けになればと思い、記事を投稿しております。
それでは、また次回お会いしましょう〜

参考

https://teratail.com/questions/63276

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

iso-2022-jpの文字列をutf-8に変換する

メールを受け取った際にiso-2022-jpで送られたために文字化けしてしまっていました。

rubyのencodeを使ってutf-8に変換します

検証するためにiso-2022-jpでエンコードされた文字列を生成します

puts "ほげ".encode('iso-2022-jp')
# => $B$[$2(B

encodeを以下のように記述します

str.encode(変換先, 変換元, 変換オプション)

今回はiso-2022-jpの文字列をutf-8に変換したいので次のようになります

str = "ほげ".encode('iso-2022-jp')
puts str
# => $B$[$2(B
puts str.encode('utf-8', 'iso-2022-jp', invalid: :replace, undef: :replace, replace: '')

今回は変換できない文字を空文字に置き換えるオプションを指定しています

変換元がわかっている場合でないとこの方法で変換することはできませんが、
今回のようにメールが文字化けしている場合などでは有効に使えそうです

参考

https://docs.ruby-lang.org/ja/latest/method/String/i/encode.html

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

Formオブジェクトを用いて作成したデータを、特定のデータのみ削除する方法

要点

  • メルカリクローンのアプリを作るときの参考に
  • dependent: :destroy を用いて外部キーの削除制限を外す
  • 下記のエラーを解消する方法
ActiveRecord::InvalidForeignKey (Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails (`-アプリ名-_development`.`-中間テーブル名-`, CONSTRAINT `fk_rails_~~~~~` FOREIGN KEY (`-商品のテーブル名-_id`) REFERENCES `-商品のテーブル名-` (`id`))):

はじめに

メルカリのようなフリマアプリを作成中で、Formオブジェクトを用いて商品にタグ付けして出品できる機能を実装する所まで行いました
各モデルとコントローラーは以下のようになります

  • Item/商品
  • Tag/タグ
  • TagItemRelation/商品とタグの中間テーブル
  • TagsItem/ ItemとTagを同時に保存するためのFormオブジェクト
/app/model/item.rb
class Item < ApplicationRecord
  has_many :tag_item_relations
  has_many :tags, through: :tag_item_relations
end
/app/model/tag.rb
class Tag < ApplicationRecord
  has_many :tag_item_relations
  has_many :items, through: :tag_item_relations
  validates :tag_name, uniqueness: true
end
/app/model/tag_item_relation.rb
class TagItemRelation < ApplicationRecord
  belongs_to :item
  belongs_to :tag
end
/app/form/tags_item.rb
class TagsItem
  include ActiveModel::Model
  attr_accessor :item_name,
                :tag_name

  with_options presence: true do
    validates :item_name
  end

  def save
    item = Item.create(item_name: item_name)
    tag = Tag.where(tag_name: tag_name).first_or_initialize
    tag.save
    TagItemRelation.create(item_id: item.id, tag_id: tag.id)
  end
end

コントローラー

/app/controller/items_controller.rb
class ItemsController < ApplicationController
  def index
    @items = Item.all.order('created_at ASC')
  end

  def new
    @item = TagsItem.new
  end

  def create
    @item = TagsItem.new(items_params)
    if @item.valid?
      @item.save
      redirect_to root_path
    else
      render :new
    end
  end

  private

  def items_params
    params.require(:tags_item).permit(
      :item_name,
      :tag_name
    )
  end
end

その後商品閲覧機能と
問題の商品削除機能を実装した

/app/controller/items_controller.rb
class ItemsController < ApplicationController
  before_action :set_item, only: [:show, :destroy]

###  中略

  def  show
    @tags_item = TagItemRelation.find(params[:id])
  end

  def destroy
    if current_user.id == @item.user_id
      @item.destroy
      redirect_to root_path
    else
      render :show
    end
  end

private

###  中略

  def set_item
    @item = Item.find(params[:id])
    @tag = Tag.find(params[:id])
  end
end

しかし実際に出品した商品を削除してみると、、、

ActiveRecord::InvalidForeignKey (Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails (`-アプリ名-_development`.`tag_item_relations`, CONSTRAINT `fk_rails_~~~~~~` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`))):

のエラーが出てしまい、商品の削除ができませんでした

調べたこと

エラー内容をよく読んでみると、商品idは中間テーブルの外部キー(foreign key)に含まれて
デリートやアップデートができないよ!と書かれているのがわかります

そこで外部キーについて調べてみると、、、
外部キー(外部制約キーとも)は、よくマイグレーションファイルなどに書いてるデータベースのキーで
データベースの特定の項目に好き勝手な値を入れることを防ぎ、外部の項目から選んで入れる制約を持ち
主に外部キーのテーブルから主キーのテーブルのデータに対して、変更を制限するものだそうです
今回の場合外部キーのテーブルはtag_item_relations、主キーのテーブルはitemになることが分かりますね

外部キーの設定変更

今回のエラーの原因はマイグレーションファイルに記載した外部キー(foreign_key: trueのやつ)でした
ですがマイグレーションファイルごと書き換えてしまうと別の不具合が起きてしまう恐れがあります
今回はdestoryで外部キーとして設定している項目を削除させるように、dependent: :destroyを設定しようと思います
この簡単な設定により不具合なく商品の削除ができるようになります

/app/model/item.rb
class Item < ApplicationRecord
  has_many :tag_item_relations, foreign_key: :item_id, dependent: :destroy
  has_many :tags, through: :tag_item_relations
end

参考にしたサイト

外部キー (foreign key)とは

関連記事

外部キー制約でハマったので(rails)
外部キーを持つデータのdestroyを行うための設定

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

RSpec エラー 解決 undefined method 'name' in 'user' factory

rspecコマンドを打った時に出たエラー。

結論 

FactoryBotの定義の書き方が違う。

コード

間違った書き方

require 'rails_helper'
FactoryBot.define do
   factory :user do
     name "Example"
     sequence(:email) { |n| "tester#{n}@example.com" }
     password "password"
     password_confirmation "password"
     year "1年"
     bio "hello!"
   end

end

正しい書き方

require 'rails_helper'
FactoryBot.define do

  factory :user do
    name {"Example"}
    sequence(:email) { |n| "tester#{n}@example.com" }
    password {"password"}
    password_confirmation {"password"}
    year {"1年"}
    bio {"hello!"}
  end
end

一応gemfile

group :test do
 #rspecには、以下の3つのgemが必要。
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'rails-controller-testing'
  #rspecのfeatureで必要。
  gem 'capybara', '~> 2.13'
  #Capybaraでテスト中に、現在どのページを開いているのか確認するため
  gem 'launchy'
  #便利。validationが一行くらいでかける。
  gem 'shoulda-matchers',
    git: 'https://github.com/thoughtbot/shoulda-matchers.git',
    branch: 'rails-5'
end

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'spring-commands-rspec'
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 4.1.0'
  # Display performance information such as SQL time and flame graphs for each request in your browser.
  # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
  gem 'rack-mini-profiler', '~> 2.0'
  gem 'listen', '~> 3.3'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'

end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

参考:
https://stackoverflow.com/questions/57126906/rails-undefined-method-name-in-user-factory-or-undefined-method-for-all-fac

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

RSpec エラー undefined method `feature' for RSpec:Module

rspecコマンドを打った時に出たエラー。

結論

capybaraを読み込んでいなかった。

コード

エラーコード

require 'rails_helper'

RSpec.feature "Users", type: :feature do

  describe "Signup page" do
    before do
      visit signup_path
    end

    it "display Signup contents, title properly" do
      expect(page).to have_css('h1', text: 'ユーザー登録')
      expect(page).to have_title 'Signup hoge'
    end
  end

end

解決したコード capybaraを読み込む

  require 'rails_helper'
+ require 'capybara/rspec'

RSpec.feature "Users", type: :feature do

  describe "Signup page" do
    before do
      visit signup_path
    end

    it "display Signup contents, title properly" do
      expect(page).to have_css('h1', text: 'ユーザー登録')
      expect(page).to have_title 'Signup hoge'
    end
  end

end

一応gemfile


#rspecのprogressbarを表示してくれる。実行のコマンド% bin/rspec spec/ --format Fuubar
gem 'fuubar'

group :test do
 #rspecには、以下の3つのgemが必要。
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'rails-controller-testing'
  #rspecのfeatureで必要。
  gem 'capybara', '~> 2.13'
  #Capybaraでテスト中に、現在どのページを開いているのか確認するため
  gem 'launchy'
  #便利。validationが一行くらいでかける。
  gem 'shoulda-matchers',
    git: 'https://github.com/thoughtbot/shoulda-matchers.git',
    branch: 'rails-5'
end

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'spring-commands-rspec'
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 4.1.0'
  # Display performance information such as SQL time and flame graphs for each request in your browser.
  # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
  gem 'rack-mini-profiler', '~> 2.0'
  gem 'listen', '~> 3.3'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'

end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

参考:

https://stackoverflow.com/questions/26217184/rspec-3-1-undefined-method-feature-for-mainobject

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

RSpec エラー 解決 undefined method `feature' for RSpec:Module

rspecコマンドを打った時に出たエラー。

結論

capybaraを読み込んでいなかった。

コード

エラーコード

require 'rails_helper'

RSpec.feature "Users", type: :feature do

  describe "Signup page" do
    before do
      visit signup_path
    end

    it "display Signup contents, title properly" do
      expect(page).to have_css('h1', text: 'ユーザー登録')
      expect(page).to have_title 'Signup hoge'
    end
  end

end

解決したコード capybaraを読み込む

  require 'rails_helper'
+ require 'capybara/rspec'

RSpec.feature "Users", type: :feature do

  describe "Signup page" do
    before do
      visit signup_path
    end

    it "display Signup contents, title properly" do
      expect(page).to have_css('h1', text: 'ユーザー登録')
      expect(page).to have_title 'Signup hoge'
    end
  end

end

一応gemfile


#rspecのprogressbarを表示してくれる。実行のコマンド% bin/rspec spec/ --format Fuubar
gem 'fuubar'

group :test do
 #rspecには、以下の3つのgemが必要。
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'rails-controller-testing'
  #rspecのfeatureで必要。
  gem 'capybara', '~> 2.13'
  #Capybaraでテスト中に、現在どのページを開いているのか確認するため
  gem 'launchy'
  #便利。validationが一行くらいでかける。
  gem 'shoulda-matchers',
    git: 'https://github.com/thoughtbot/shoulda-matchers.git',
    branch: 'rails-5'
end

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'spring-commands-rspec'
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 4.1.0'
  # Display performance information such as SQL time and flame graphs for each request in your browser.
  # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
  gem 'rack-mini-profiler', '~> 2.0'
  gem 'listen', '~> 3.3'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'

end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

参考:

https://stackoverflow.com/questions/26217184/rspec-3-1-undefined-method-feature-for-mainobject

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

ハッシュ

ハッシュとは

データとそれに対応する名前をつけて複数の値を管理するものです。
データの部分を「バリュー」、名前の部分を「キー」とよびます。

このようなキーとバリューで管理する考え方を「キーバリューストア」と呼びます。

ハッシュは { } (波括弧)を使って生成します。

ハッシュロケット => を用いてキーとバリューをセットにして書きます。
変数 = { キー1 => バリュー1, キー2 => バリュー2, キー3 => バリュー3 }

ハッシュの書き方には複数あり、シンボルという物を使って書かれる書き方もあります。

シンボルは、見た目は文字列のようですが、実際の中身は数値になっています。

シンボルの宣言は、文字列の先頭にコロン:をつけます。
同じ文字列であれば、文字列にクォーテーションをつけてもつけなくても中身は同じ値になります。

:"Hello"

:Hello

#どちらも同じ値

コンピューターは、文字列を扱うよりも、数値を扱うほうが処理速度は速くなるので、処理速度を速くすることと、文字列としての役割も果たせることから、ハッシュのキーには、文字列よりもシンボルを用いることが多いのです。

実際にハッシュを生成してみましょう。

son = { "name" => "Nick", "age" => 12 }
father = { name: "George", age: 38 }

ハッシュへ要素を追加したい場合

ハッシュ[追加するキー] = 値 とします。

son = { "name" => "Nick", "age" => 12 }
father = { name: "George", age: 38 }

father[:hobby] = "fishing"    #キー hobby バリュー fishing を追加

ハッシュの値を取得

ハッシュ[取得したい値のキー] とします。

son = { "name" => "Nick", "age" => 12 }
father = { name: "George", age: 38, hobby: "fishing" }

puts father[:age]  # 38 と出力される

ハッシュの値を変更

ハッシュ[変更したい値のキー] = 値 とします。

son = { "name" => "Nick", "age" => 12 }
father = { name: "George", age: 38, hobby: "fishing" }

father[:age] = 40

puts father[:age]  #40と出力される

以上です。

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

TailwindUIのFormの見た目がサンプル通りにならない

困ったこと

同じスタイルクラスを当ててるはずなのに、見た目が違う。

理想の見た目

image.png

実際の見た目

image.png

対応内容

たぶん次のどっちかやればええんちゃうか

https://github.com/tailwindlabs/tailwindcss-forms
https://tailwindcss-custom-forms.netlify.app/

1個目のほうをインストールしたけど怒られた

terminal
ERROR in ./node_modules/tippy.js/animations/perspective.css (./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./node_modules/tippy.js/animations/perspective.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
TypeError: Cannot read property 'none' of undefined
    at /Users/XXXXXXX/projects/XXXXX/node_modules/@tailwindcss/forms/src/index.js:38:26
    at /Users/XXXXXXX/projects/XXXXX/node_modules/tailwindcss/lib/util/processPlugins.js:66:5
    at Array.forEach (<anonymous>)
    at _default (/Users/XXXXXXX/projects/XXXXX/node_modules/tailwindcss/lib/util/processPlugins.js:60:11)
    at /Users/XXXXXXX/projects/XXXXX/node_modules/tailwindcss/lib/processTailwindFeatures.js:56:54
    at LazyResult.run (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:288:14)
    at LazyResult.asyncTick (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:212:26)
    at LazyResult.asyncTick (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:225:14)
    at /Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:217:17

ERROR in ./app/javascript/css/tailwind.css (./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./app/javascript/css/tailwind.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
TypeError: getProcessedPlugins is not a function
    at /Users/XXXXXXX/projects/XXXXX/node_modules/tailwindcss/lib/processTailwindFeatures.js:67:83
    at LazyResult.run (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:288:14)
    at LazyResult.asyncTick (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:212:26)
    at LazyResult.asyncTick (/Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:225:14)
    at /Users/XXXXXXX/projects/XXXXX/node_modules/postcss/lib/lazy-result.js:217:17
ℹ 「wdm」: Failed to compile.

Tailwind CSS v2.0. 用だからかな??

Tailwind CSS v2.0インストール

下記を元に2.0を入れる
https://tailwindcss.com/docs/installation

Terminal
npm install tailwindcss@latest postcss@latest autoprefixer@latest

下記怒られる。

Terminal
> % bin/webpack-dev-server
ℹ 「wds」: Project is running at http://localhost:3035/
ℹ 「wds」: webpack output is served from /packs/
ℹ 「wds」: Content not from webpack is served from /Users/XXX/projects/XXX/public/packs
ℹ 「wds」: 404s will fallback to /index.html
ℹ 「wdm」: wait until bundle finished: /packs/js/application-83db7df6641884880765.js
✖ 「wdm」: Hash: f6dfc3f28099b484dcf6
Version: webpack 4.44.2
Time: 3149ms
Built at: 2021-01-13 7:49:08
                                     Asset       Size       Chunks                         Chunk Names
    js/application-83db7df6641884880765.js   1.88 MiB  application  [emitted] [immutable]  application
js/application-83db7df6641884880765.js.map   1.92 MiB  application  [emitted] [dev]        application
                             manifest.json  364 bytes               [emitted]              

ERROR in ./app/javascript/css/tailwind.css (./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./app/javascript/css/tailwind.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
Error: PostCSS plugin tailwindcss requires PostCSS 8. Update PostCSS or downgrade this plugin.
    at Processor.normalize (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/processor.js:153:15)
    at new Processor (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/processor.js:56:25)
    at postcss (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/postcss.js:55:10)
    at /Users/XXX/projects/XXX/node_modules/postcss-loader/src/index.js:140:12

ERROR in ./node_modules/tippy.js/animations/perspective.css (./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./node_modules/tippy.js/animations/perspective.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
Error: PostCSS plugin tailwindcss requires PostCSS 8. Update PostCSS or downgrade this plugin.
    at Processor.normalize (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/processor.js:153:15)
    at new Processor (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/processor.js:56:25)
    at postcss (/Users/XXX/projects/XXX/node_modules/postcss-loader/node_modules/postcss/lib/postcss.js:55:10)
    at /Users/XXX/projects/XXX/node_modules/postcss-loader/src/index.js:140:12

インストール方法にも書いてるが、こちらを参考にする。

Terminal
npm uninstall tailwindcss postcss autoprefixer
npm install tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }
}
terminal
-> % npx tailwindcss init

   @tailwindcss/postcss7-compat 2.0.2

   ? tailwind.config.js already exists.
tailwind.config.js
module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}

既に下記だった

app/javascript/css/tailwind.css
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

一行目追加

app/javascript/src/js/application.js
import 'tailwindcss/tailwind.css' 
import './dropdown.js'

もう一度、tailwindcss-formsインストールする

今見直したら、2つとも叩いたが、片方でいいと思う。

Terminal
-> % npm install @tailwindcss/forms
+ @tailwindcss/forms@0.2.1
added 2 packages from 1 contributor, removed 7 packages and audited 1824 packages in 8.861s
found 0 vulnerabilities

-> % yarn add @tailwindcss/forms
yarn add v1.22.10
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
[1/4] ?  Resolving packages...
[2/4] ?  Fetching packages...
[3/4] ?  Linking dependencies...
warning " > webpack-dev-server@3.11.1" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "webpack-dev-server > webpack-dev-middleware@3.7.2" has unmet peer dependency "webpack@^4.0.0".
[4/4] ?  Building fresh packages...
success Saved lockfile.
success Saved 4 new dependencies.
info Direct dependencies
├─ @tailwindcss/forms@0.2.1
├─ @tailwindcss/postcss7-compat@2.0.2
└─ tailwindcss@2.0.2
info All dependencies
├─ @tailwindcss/forms@0.2.1
├─ @tailwindcss/postcss7-compat@2.0.2
├─ mini-svg-data-uri@1.2.3
└─ tailwindcss@2.0.2
✨  Done in 19.76s.
tailwind.config.js
module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [
    require('@tailwindcss/forms'),
  ],
}

結果

いい感じになった。

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

C#で書くものはRubyでこう書く

注意

個人的によく使う書き方のみ記述します。
まだ未完成です。暇だった時に更新します。

標準ストリーム

標準出力

C#
Console.Write("改行なし");
Console.WriteLine("改行あり");
Ruby
print "改行なし"
puts  "改行あり"

標準入力

C#
// 値がひとつだけの場合
var single = Console.ReadLine();
// 複数ある場合
var multiple = Console.ReadLine().Split(",");
Ruby
# 値がひとつだけの場合
single = gets
# 複数ある場合
multiple = gets.split(",")

空白で区切られてるだけの場合、こういう書き方もできるらしい。
multiple = gets.split
Rubyって凄いね。

ループ処理

for文

C#
for (var i = 0; i < 10; i++)
{
    Console.WriteLine(i);
}
Ruby
for i in 0..9
    puts i
end

foreach文

C#
var array = new int[5] {1, 2, 3, 4, 5};

foreach (var i in array)
{
    Console.WriteLine(i);
}
Ruby
array = (1..5)

array.each do |i|
    puts i
end

While文

C#
var num = 0;

while (num < 5)
{
    Console.WriteLine(num);
    num++;
}
Ruby
num = 0

while num < 5 
    puts num
    num += 1
end

Rubyにはインクリメントとデクリメントがないみたい。
その代わり?1を追加するのとは別で、succとnextてのがあるらしい。

succ
Ruby
num = 1
apt = "a"
letter = "z"

puts num.succ       # 出力: 2
puts apt.succ       # 出力: b
puts letter.succ    # 出力: aa

これはこれで便利。zの次がaaってのも面白い(何処で使うかわからないけど……)
ただ、デクリメントに対応するものはないっぽい。
近いものがあるみたい……

pred
Ruby
num = 1
puts num.pred       # 出力: 0

ただ、アルファベットでやるとエラーが出た。
まぁそんな問題はないはず!

条件分岐

if文

C#
var name = "たけし";

if (name == "たけし")
{
    Console.WriteLine("ガタガタガタガタガタガタガタ");
}
else if (name == "たくろう")
{
    Console.WriteLine("残念なイケメン");
}
else
{
    Console.WriteLine("みか");
}
Ruby
name = "たけし"

if name == "たけし"
    puts "ガタガタガタガタガタガタガタ"

elsif name == "たくろう"
    puts "残念なイケメン"

else
    puts "みか" 
end

個人的にRubyはちょっと見にくいかも……

Switch文

C#
var energyDrink = "Monster";

switch (energyDrink)
{
    case "Monster":
        Console.WriteLine("いっぱい味がある");
        break;

    case "Zone":
        Console.WriteLine("安くてデカい");
        break;

    default:
        Console.WriteLine("レッドブルが値下げするらしい");
        break;
}
Ruby
energyDrink = "Monster"

case energyDrink

    when "Monster"
        puts "いっぱい味がある"

    when "Zone"
        puts "安くてデカい"

    else
        puts "レッドブルが値下げするらしい"
end

いちいちbreak書くのめんどくさかったからありがたい。

三項演算子

C#
var num = 10;
var results = (num == 10) ? "true" : "false";
Ruby
num = 10;
results = (num == 10) ? "true" : "false";

こいつは流石に変わらなかった。

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

【Rails】RSpec キホンのキ

はじめに

RSpecはRailsで定番のテストフレームワーク。
minitestと比べると必要なGemが多い。
とはいえ一度に全てのGemを入れると、どのGemがどこでどういう働きをするのかが曖昧になってしまう。
そこで、RSpecの学習に至っては、テストの内容に応じてGemを逐次追加していく手法を取ることにした。

RSpecとFactoryBot

Gemの導入と設定

RSpecと、テスト用データの生成用となるFactoryBotを導入する。
FactoryBotはminitestにおけるfixture的な役割を担う。
ここでspring-commands-rspecを入れておくと、RSpecをbinstubから実行し、springを使ってテスト開始にかかる時間を短縮できるとのこと。

Gemfile.rb
group :development, :test do
  gem "rspec-rails"
  gem 'spring-commands-rspec'
  gem "factory_bot_rails"
end

bundle installしたら以下のコマンドでRSpec用のデータを生成する。

$ bundle exec rails generate rspec:install

RSpecの設定を追加する。
設定はconfig/application.rbに書く。
ここではgenerateコマンド実行時にRSpec用のファイルを生成するかを設定している。
truefalseかはプロジェクトによって適宜変更する。

config/application.rb
module SampleApp
  class Application < Rails::Application
    .
    .
    .
    config.generators do |g|
      g.test_framework :rspec,
        controller_specs: true,
        fixtures: true,
        helper_specs: true,
        model_specs: true,
        request_spec: true,
        routing_specs: false,
        view_specs: false
    end
  end
end

次にFactoryBotの設定。
以下のように記述することで、FactoryBotクラスの呼び出しを簡略化できる。

spec/rails_helper.rb
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end
# FactoryBotクラスの呼び出し
user = FactoryBot.create(:user)

#省略版 
user = create(:user)

テスト用データの設定をもう一つ。
以下の一行のコメントアウトを外しておく。
spec/support/配下のファイルを読み込めるようになる。
ここにはテスト用のヘルパーを記述するファイルなどが置かれる。

spec/rails_helper.rb
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

最後に、RSpecのbinstubを導入する。
これで$ bundle exec rspec spec/に加えて$ bin/rspec spec/でテストを実行できるようになる。

$ bundle exec spring binstub rspec

# 実行コマンド
$ bin/rspec spec/

specファイルの生成

RSpecで使用されるファイルは全て*_spec.rbというファイル名になっている。
これをコントローラやモデルに合わせて各種用意していく。
例えばusers_controller用のspecファイルは以下のように生成する。

$ rails g rspec:controller users
    create  spec/controllers/users_controller_spec.rb

テスト用データの用意

FactoryBotを使って、各種クラスのテスト用データ(インスタンス)を定義する。
例えばUserクラスなら、/spec/factories/users.rbに定義していく。

/spec/factories/users.rb
FactoryBot.define do

  # Userモデルのテストデータmichaelを定義
  factory :michael, class: User do
    name 'michael'
    email 'michael@example.com'
  end

  # 汎用データをたくさん用意する
  factory :user do
    sequence(:name) { |n| "name-#{n}"}
    sequence(:email) { |n| "test-#{n}@example.com"}
  end
end

定義したテストデータを呼び出してインスタンス変数に格納し、テストで使用する。

/spec/models/users_spec.rb
RSpec.describe User, type: :model do
  before do
    # モデルのみの作成
    @michael = build(:michael)
    # DBへレコード生成
    @michael = create(:michael)

    # 汎用データの活用
    @user1 = build(:user)
    @user2 = build(:user)
    @user3 = build(:user)
  end
  .
  .
  .
end

続く!!!

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

Ruby製REST-like APIフレームワーク「grape」の最小構成 (RSpec入り / Heroku対応)

GET /ping{"response": "pong"} を返すAPIサーバを、Ruby製REST-like APIフレームワーク「grape」で作成する最小構成。

ファイル/ディレクトリ構成

.
├── Gemfile
├── Procfile
├── config.ru
├── api.rb
└── spec
    ├── spec_helper.rb
    └── api_spec.rb

ユニットテスト

bundle exec rspec spec/api_spec.rb

ローカル開発

bundle exec rackup
## もしくは
heroku local:start

ファイル解説

Gemfile

bundle init で作成した後、以下を行う。

  • Ruby のバージョン指定 (Heroku向け)
  • grape、rspec、puma の指定
  • rack-test はユニットテスト(RSpec)向け
Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

ruby "~> 3.0.0"

gem "grape"
gem "rspec"
gem "puma"

group :development do
  gem "rack-test"
end

Procfile

Procfile は Heroku 向け。Herokuを使わない場合は不要。

Procfile
web: bundle exec rackup config.ru -p $PORT

config.ru

rackup 向け。

config.ru
require_relative File.join("api")
run APIserver

api.rb

API本体。

api.rb
require "grape"

class APIserver < Grape::API
  format :json

  get "/ping" do
    {"response": "pong"}
  end
end

spec/spec_helper.rb

RSpec向け。
require_relative は config.ru と同じものを指定していく。

require "rack/test" 以下は grape/RSpec に書いてある通り、おまじない的。 app が API のインスタンスを返すようにする必要がある。

spec/spec_helper.rb
require_relative File.join("..", "api")

require "rack/test"
include Rack::Test::Methods
def app
  APIserver
end

spec/api_spec.rb

ユニットテスト本体。

spec/api_spec.rb
require "spec_helper"

context "GET ping" do
  it "return pong" do
    get "/ping"
    expect(last_response.status).to eq(200)
    expect(JSON.parse(last_response.body)).to eq({"response" => "pong"})
  end
end

今後の拡張ポイント

rackup(webサーバ)のエントリーポイントは config.ru、RSpec(ユニットテスト)のエントリーポイントは spec_helper.rb です。require等はここに集中する(もしくは共通化)するようにしましょう。

APIのデータ構造を実装したければ、grape-entityが使えます。Nestedな1:N構造も実装可能です。RESTful APIにおいて一対多関係のデータ構造をGrape::Entityを使って表現する

複雑なJSONの検証には rspec-json_matcher が使えます。複雑なケースの例は rspec-json_matcherでJSONの検証を自由自在に行う をご覧ください。

EoT

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