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

「Rails s」でPostgreSQLが実行できなくなった

エラー状況

当方、自分が所有しているMacにて、RailsでWEBアプリを開発しているのですが、
突如、「Rails s」コマンドでエラーが発生し、Railsが起動できなくなってしまった。

エラーメッセージ

psql: could not connect to server: No such file or directory
    Is the server running locally and accepting
    connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

■日本語訳
psql:サーバーに接続できませんでした:そのようなファイルまたはディレクトリはありません。
サーバーはローカルで実行されており、Unixドメインソケット「/tmp/.s.PGSQL.5432」で接続を受け入れていますか?

エラー発生までの経緯

昨日までは、エラーも出ず正常にrailsが起動できた(その後、OSをシャットダウン)が、
翌日、OSを起動したら「Rails s」が使用できなくなった。

試してみたこと

  • OSの再起動
  • PostgreSQLの再起動(「brew services stop/start」コマンドを実施)
  • 「Rails c」、「Rails db」コマンドなどのrailsコマンドでも同事象が発生
  • Railsコマンドを使わず、PostgreSQLコマンド(psql)でも同事象が発生

当方の開発環境

項目 バージョン or 詳細
MacOS Catalina 10.15.6
パッケージ管理システム Homebrew
PostgreSQL 11.9
Ruby 2.6.5
Ruby on Rails 5.2.4

解決した方法

以下の方法で解決しました

パッケージ管理システムは「Homebrew」
#PostgreSQLのサービスを停止
brew services stop postgresql

#クラッシュファイルを削除
rm /usr/local/var/postgres/postmaster.pid 

#PostgreSQLのサービスを起動
brew services start postgresql

そのほかの解決方法

上記の方法で解決しなかった場合は、PostgreSQLの再インストールを実施しました。

パッケージ管理システムは「Homebrew」
#PostgreSQLのアンインストール
brew uninstall --force postgresql

#PostgreSQLの残ファイルを削除
ll /usr/local/var/postgres
rm -rf /usr/local/var/postgres

#PostgreSQLのインストール
brew install postgresql

原因

当方の場合、プロセスがクラッシュした際に起因する「postmaster.pid」ファイルが残っていたため、エラーが発生していたようです。
いわゆるゴミファイルが残ってしまっているため、PostgreSQLが起動できなかったようです。
エラー発生の傾向として、OSの起動後にrailsコマンドが使用できなくなったことから、
前回のOSのシャットダウンの時に、OSまたはPostgreSQLがクラッシュし、PostgreSQLが正常に停止できなかった可能性が考えられます。

参考文献

PostgreSQLがMacで実行されていない
PostgreSQLのインストールから起動/停止まで
[MacOS] PostgreSQL の全バージョンをアンインストールする方法 ~ Homebrew 編

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

【Ruby】カラムの名前間違えた&制約間違えた〜助けてmigrate〜

解決したいこと

アプリケーションを作り始めて2日目。
DB設計を終えてテーブルを作り、モデルを作り、ふと気付く。

「あれ?スペル違ってるじゃないの・・・」
「あれ?null制約かかっててコンソールからデータ追加できないじゃないの・・・」

さてこれはどうしたものかと色々調べた結果、名前だけ変えられる素晴らしいコマンドがあるとのこと。
ありがたやありがたや・・・。

該当するソースコード

class CreateParties < ActiveRecord::Migration[6.0]
  def change
    create_table :parties do |t|
      t.string  :name,          null: false
      t.text    :iintroduction, null: false    #⬅️①なぜかiが多いスペルミス
      t.integer :season_id,     null: false
      t.integer :country_id,    null: false
      t.integer :genre_id,      null: false
      t.text    :picture,       null: false    #⬅️②これも修正したい
      t.timestamps
    end
  end
end

まずは①からターミナルで実行。

% rails generate migration rename_iintroduction_column_to_parties
                                 ⬆️変えたいカラム名          ⬆️モデル名

結果がこちら。
新しくrename用のマイグレーションファイルを作ってくれます。

Running via Spring preloader in process 9848
      invoke  active_record
      create    db/migrate/20210108122152_rename_iintroduction_column_to_parties.rb

次に作成してくれたファイルに記述していきます。

class RenameIintroductionColumnToParties < ActiveRecord::Migration[6.0]

  def change
    rename_column :parties, :iintroduction, :introduction
  end             ⬆️モデル名  ⬆️変えたいカラム名  ⬆️修正後のカラム名
end

記述できたらマイグレーション。

% rails db:migrate

直った!
次は②のnull:false制約をつけてしまったものを外したいという作業。
なぜかというとレビューサイトを作っているのですが、レビューしたいデータは管理者のみが作成できるようにしたいため、ひとまずデータの投稿をコンソールから行いたかったからです。
そこで画像データをコンソールから入力しようとしたところnull:false制約がついているためコンソールからデータ入力ができずに困っておりました。
一度この制約を外してとにかく画面上に一つでもデータが表示されるようにしたいというのもあったため、取り急ぎ外すことに。
こちらも同じようなコマンドで対応可能でした。

 % bin/rails g migration ChangeColumnToAllowNull

同じくマイグレーションファイルが作成されるのでそちらに記述。

class ChangeColumnToAllowNull < ActiveRecord::Migration[6.0]

  def up
    change_column_null :parties, :picture, null: true   #「up」でnull: trueに変更しますよ、という意味
  end

  def down
    change_column_null :parties, :picture, null: false  #「down」でnull: false制約つきのものから⬆️⬆️⬆️
  end

end

記述できたらマイグレーション。

% rails db:migrate

これで制約を外すことができたのでデータを追加することができました。
そして今、わたしの目の前にはいざ画像を追加しようと思ったら容量が大きすぎて追加できないというエラーが発生しております。
さあ次の戦場へ向かおう。

参考にさせて頂いた記事

https://qiita.com/libertyu/items/93acd8733e34b1d0a63c
https://qiita.com/mom0tomo/items/31466a80ca38db4ebf8c

ありがとうございました。

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

ActiveHash

ActiveHash の使い方 まとめ

Active_Hashとは、、、
都道府県名などの変更されないデータを「モデルファイル内」に直接記述することで、
データベースへ保存せずにデータを取り扱うことができる Gem のこと。

ActiveHash導入方法

Gemfileを編集

Gemfile
gem 'active_hash'

記述したらbundle installを実行

例 ArticleモデルでActiveHashを導入

記事を管理するArticleモデル
記事のジャンルを管理するGenreモデル
記事のジャンルは変更されないデータ=ActiveHashを用いて管理

①それぞれのモデルを作成
rails g model article
rails g model genre --skip-migration

--skip-migrationとは??
モデルファイルを作成するときに、マイグレーションファイルの生成を行わないためのオプション。今回、記事のジャンルの情報はデータベースに保存しない=マイグレーションファイルを作成する必要はない。

②genreモデルでクラスを定義し、ActiveHash::Base を継承するための記述を行う

ジャンルのデータは、配列にハッシュ形式で格納

models/genre.rb
class Genre < ActiveHash::Base
 self.data = [
   { id: 1, name: '--' },
   { id: 2, name: '経済' },
   { id: 3, name: '政治' },
   { id: 4, name: '地域' },
   { id: 5, name: '国際' },
   { id: 6, name: 'IT' },
   { id: 7, name: 'エンタメ' },
   { id: 8, name: 'スポーツ' },
   { id: 9, name: 'グルメ' },
   { id: 10, name: 'その他' }
 ]
 end
③①で生成されたarticleのマイグレーションファイルを編集・マイグレートする

以下の様に編集し ⇨  rails db:migrate
Articlesテーブルの中にgenre_idという名前のカラムを作成しているのは、投稿した記事を表示する際に、その記事に紐付いたジャンルを取得するため
Articleテーブルの中で、Genreモデル(ActiveHash)のidを外部キーとして管理することで、その記事に紐付いたジャンルの取得が実現

db/migrate/20XXXXXXXXXXXX_create_articles.rb
class CreateArticles < ActiveRecord::Migration[6.0]
 def change
   create_table :articles do |t|
     t.string     :title        , null: false
     t.text       :text         , null: false
     t.integer    :genre_id     , null: false
     t.timestamps
   end
 end
end
④モデル間でのアソシエーションの設定

ActiveHashを用いてアソシエーションを設定する場合は、ActiveHashで定義されているmoduleをモデルに取り込む必要がある。

1)ActiveHashを導入したい(される)モデル
投稿する記事=Articleは、1つのジャンル=Genreに紐付いています。そのため、Articleモデルにbelongs_toを設定します。
ActiveHashを用いて、belongs_toを設定するには、
extend ActiveHash::Associations::ActiveRecordExtensionsと記述してmoduleを取り込みます。

app/models/article.rb
class Article < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :genre
end

2)ActiveHashを設定したモデル
1つのジャンル=Genreは、たくさんの投稿物=Articlesに紐付いています。そのため、Genreモデルにはhas_manyを設定します。

ActiveHashを用いて、has_manyを設定するには、
include ActiveHash::Associationsと記述してmoduleを取り込みます。

app/models/genre.rb
class Genre < ActiveHash::Base
 self.data = [
   { id: 1, name: '--' },
  〜〜〜省略〜〜〜
   { id: 10, name: 'その他' }
 ]

  include ActiveHash::Associations
  has_many :articles

 end

※moduleとは、特定の役割を持つメソッドや定数に名前を付けてまとめたもの。どのようなmoduleが定義されているかは、こちらのリファレンスで確認。

※ActiveHashを用いたアソシエーションの設定は、他にもあります。詳しくはこちらのドキュメントを確認。

⑤バリデーションを設定

データベースに空の投稿が保存されないようにする場合、バリデーションヘルパーのnumericality(数値かどうかを検証する)を用いる。数値であればデータベースに保存を許可して、それ以外では保存が許可されないようにできます。今回においては、--を保存されないようにしたいので、id: 1以外であれば保存できるように設定すると

app/models/article.rb
class Article < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :genre

  #空の投稿を保存できないようにする
  validates :title, :text, presence: true

  #ジャンルの選択が「--」の時は保存できないようにする
  validates :genre_id, numericality: { other_than: 1 } 
end

このバリデーションは、genre_idのid:1以外のときに保存できるという意味になる。

導入については以上です。ジャンル選択のプルダウン生成などはview関連になるため
別記事に追記予定。

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

Ruby on Rails 日本語でフォーム投稿した際にIncorrect string valueエラー解決方法

はじめに

環境

  • Docker
  • ruby 2.3.7
  • Rails 5.2.4.4
  • MySQL 5.7.32

エラー内容

新規投稿フォームに日本語を入力し、送信するとIncorrect string valueエラーが発生。
スクリーンショット 2021-01-04 21.28.20.png

原因

Incorrect string valueのエラー文から、フォームに入力した文字列が原因と推測。
試しに英語を入力して送信すると問題なく投稿できました。

ということは、DBが日本語に対応していない?

MySQLに接続し、使用中のデータベースの設定を確認。

mysql> show variables like '%char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | latin1                     |
| character_set_connection | latin1                     |
| character_set_database   | latin1                     |
| character_set_filesystem | binary                     |
| character_set_results    | latin1                     |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

DBの文字コードの設定がutf8ではなく、latin1になっていました。
latin1は、西ヨーロッパ言語で日本語対応していないらしいです。

Docker環境構築時にdatabase.ymlファイルの文字コードの設定が抜けていたのが原因でした。

database.yml
default: &default
  adapter: mysql2
  # ここに「charset: utf8」が抜けてたのが原因
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: hideki1104
  host: db

解決方法

etc/mysql/my.cnfのファイルに

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_bin
skip-character-set-client-handshake
[mysqldump]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4

を記述すると文字コードが変更できます。

viエディタを使用して記述を追加しようとしたが、viコマンドが設定されておらず使えない

# vi my.cnf
/bin/sh: 5: vi: not found

apt_getを使用すればvimをインストールできるらしい。

# apt-get -v
apt 1.8.2.2 (amd64)

下記のコマンドで一旦アップデートを行い

# apt-get update

vimをインストールします。

# apt-get install vim

これでvimを使用できるようになりました。

etc/mysql/my.cnfに先ほどの

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_bin
skip-character-set-client-handshake
[mysqldump]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4

を記述することで文字コードを変更できました。

この状態ではすでに作成しているすでにDB、テーブル、カラムの文字カードは変更されていません。

MySQLの中で以下のコマンドを打ち込みました
DBの文字コード設定

ALTER DATABASE DB名 CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

テーブルの文字コード設定

ALTER TABLE テーブル名 CONVERT TO character SET utf8mb4 COLLATE utf8mb4_unicode_ci;

カラムの文字コード設定

ALTER TABLE テーブル名 CHANGE column_name カラム名 VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

これで投稿フォームで日本語を送信することができるようになりました。

参考記事

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

エラーメッセージを日本語に変換 複数テーブル対応

前提

エラーメッセージを日本語にする際、ネストしたモデルを日本語変換する際に苦戦したため備忘録として書きます。

親テーブル

recipes
id
title
description

子テーブル

ingredients
id
name
amount
recipe_id(FK)

参考サイト

https://qiita.com/satreu16/items/a072a4be415f30087ed7
https://blog.cloud-acct.com/posts/u-rails-error-messages-jayml/
https://qiita.com/Ushinji/items/242bfba84df7a5a67d5b

方法

railsを日本語化するgemです。

Gemfile
gem 'rails-i18n'

bundleします。
下記の二行を追記します。

config/application.rb
config.load_defaults 6.0

#追記
config.i18n.default_locale = :ja
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.yml').to_s]

子テーブルはattributesに複数形で記述します。

config/locales/models/ja.yml
ja:
  activerecord:
    models:
      recipe: レシピ
    attributes:
      recipe:
        title: 料理名
        description: コメント
      ingredients:
        name: 材料
        amount: 

これで日本語に対応しているはずなので、フォームで確認しましょう。

deviseを日本語化

config/locales/devise.ja.ymlを作成し、下記の内容を貼り付けます。

config/locales/devise.ja.yml
ja:
  devise:
    confirmations:
      confirmed: 'アカウントを登録しました。'
      send_instructions: 'アカウントの有効化について数分以内にメールでご連絡します。'
      send_paranoid_instructions: "あなたのメールアドレスが登録済みの場合、本人確認用のメールが数分以内に送信されます。"
    failure:
      already_authenticated: 'すでにログインしています。'
      inactive: 'アカウントが有効化されていません。メールに記載された手順にしたがって、アカウントを有効化してください。'
      invalid: "%{authentication_keys} もしくはパスワードが不正です。"
      locked: 'あなたのアカウントは凍結されています。'
      last_attempt: 'あなたのアカウントが凍結される前に、複数回の操作がおこなわれています。'
      not_found_in_database: "%{authentication_keys} もしくはパスワードが不正です。"
      timeout: 'セッションがタイムアウトしました。もう一度ログインしてください。'
      unauthenticated: 'アカウント登録もしくはログインしてください。'
      unconfirmed: 'メールアドレスの本人確認が必要です。'
    mailer:
      confirmation_instructions:
        subject: 'アカウントの有効化について'
      reset_password_instructions:
        subject: 'パスワードの再設定について'
      unlock_instructions:
        subject: 'アカウントの凍結解除について'
      password_change:
        subject: 'パスワードの変更について'
    omniauth_callbacks:
      failure: "%{kind} アカウントによる認証に失敗しました。理由:(%{reason})"
      success: "%{kind} アカウントによる認証に成功しました。"
    passwords:
      no_token: "このページにはアクセスできません。パスワード再設定メールのリンクからアクセスされた場合には、URL をご確認ください。"
      send_instructions: 'パスワードの再設定について数分以内にメールでご連絡いたします。'
      send_paranoid_instructions: "あなたのメールアドレスが登録済みの場合、パスワード再設定用のメールが数分以内に送信されます。"
      updated: 'パスワードが正しく変更されました。'
      updated_not_active: 'パスワードが正しく変更されました。'
    registrations:
      destroyed: 'アカウントを削除しました。またのご利用をお待ちしております。'
      signed_up: 'アカウント登録が完了しました。'
      signed_up_but_inactive: 'ログインするためには、アカウントを有効化してください。'
      signed_up_but_locked: 'アカウントが凍結されているためログインできません。'
      signed_up_but_unconfirmed: '本人確認用のメールを送信しました。メール内のリンクからアカウントを有効化させてください。'
      update_needs_confirmation: 'アカウント情報を変更しました。変更されたメールアドレスの本人確認のため、本人確認用メールより確認処理をおこなってください。'
      updated: 'アカウント情報を変更しました。'
    sessions:
      signed_in: 'ログインしました。'
      signed_out: 'ログアウトしました。'
      already_signed_out: '既にログアウト済みです。'
    unlocks:
      send_instructions: 'アカウントの凍結解除方法を数分以内にメールでご連絡します。'
      send_paranoid_instructions: 'アカウントが見つかった場合、アカウントの凍結解除方法を数分以内にメールでご連絡します。'
      unlocked: 'アカウントを凍結解除しました。'
  errors:
    messages:
      already_confirmed: 'は既に登録済みです。ログインしてください。'
      confirmation_period_expired: "の期限が切れました。%{period} までに確認する必要があります。 新しくリクエストしてください。"
      expired: 'の有効期限が切れました。新しくリクエストしてください。'
      not_found: 'は見つかりませんでした。'
      not_locked: 'は凍結されていません。'
      not_saved:
        one: "エラーが発生したため %{resource} は保存されませんでした:"
        other: "%{count} 件のエラーが発生したため %{resource} は保存されませんでした:"
      taken: "は既に使用されています。"
      blank: "が入力されていません。"
      too_short: "は%{count}文字以上に設定して下さい。"
      too_long: "は%{count}文字以下に設定して下さい。"
      invalid: "は有効でありません。"
      confirmation: "が内容とあっていません。"

Userモデルを追記します。

config/locales/models/ja.yml
ja:
  activerecord:
    models:
      recipe: レシピ
      user: ユーザー
    attributes:
      recipe:
        title: 料理名
        description: コメント
      ingredients:
        name: 材料
        amount: 量
      user:
        name: ユーザー名
        email: メールアドレス
        password: パスワード
        password_confirmation: 確認用パスワード
        remember_me: 次回から自動的にログイン

ユーザ名は人によってカラム名がnameでない可能性があるので注意。
これで完成です。

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

Progate Ruby 学習コースⅢ 修了

今日の感想

Rubyというかプログラミングをきちんと勉強し始めてまだほんの数日です。
コードをシンプルに・読みやすくするために異なる書き方があって、なーんとなく便利そうだなということはわかるけど、これがどのようにアプリケーションの制作作業を楽にするのかは全くイメージが湧かない…。まあまともなプログラミングをしたことがないのでイメージが湧かないなんて当然といえば当然ですが…
Rubyの学習をはじめたばかりだってことはわかっているものの、以前少しかじったPythonより若干書き方が複雑な印象があります、なんとなく…(現時点で)
レッスンごとに壁にぶち当たりながら学習を進めていると、海外の大学院に進学することを決意してから苦手だった英文法を基礎の基礎から学び直した時の感覚を思い出します。今では笑っちゃうけど、最初は一般動詞とBe動詞の違いすら合点がいかずウンウン唸ってました…

学んだこと

  • メソッドの使い方
    • メソッドの定義→メソッドの呼び出し
    • メソッドの定義は「def メソッド名」
    • メソッドの呼び出しはメソッド名を直接タイプ
  • 引数(ひきすう)の利用
    • 引数は複数受け取ることも可能
    • 戻り値の使用・return
    • 戻り値で真偽値を返すメソッドはメソッド名の末尾に?をつける(慣習として)
  • Returnによってメソッド処理を終了させる機能
  • キーワード引数を持つメソッドの書き方
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】閏年判定プログラム

概要

西暦と月を出力するプログラムを書きました。そのときに閏年を考慮する必要があります。

目次

  • 閏年(うるう年)

  • 実践

    • 問題
      • 条件
    • 解答
  • 補足

  • 参考文献

閏年(うるう年)

閏年は次の条件で判定することができます。

  • ①西暦が4で割り切れる時
  • ②ただし、100で割り切れるときは平年
  • ③ただし、400で割り切れるときは閏年

実践

問題

西暦と月を入力し、その月の日数を求めるプログラムを書いてください。

条件

  • 閏年を考慮してください

解答

def leap_year?(year, month)
  # 各月の日数
  month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  # 配列から日数を取得
  days = month_days[month - 1]

  # 2月
  if month == 2
    # 条件① 4で割り切れるとき
    if year % 4 == 0
      # 条件②,③ 西暦が100で割り切れるとき かつ 400で割り切れないとき
      if year % 100 == 0 && year % 400 != 0
        days      # うるう年ではない
      else
        days + 1  # 28日に +1 すると29日になる
      end
    # 4で割り切れないときはうるう年ではない
    else
      days
    end

  # 2月以外のとき
  else
    days
  end
end

# 西暦を入力
p '年を入力してください'
year = gets.to_i

# 月を入力
p '月を入力してください'
month = gets.to_i

# メソッド呼び出し
days = leap_year?(year, month)
p "#{year}#{month}月は#{days}日間あります"

補足

②ただし、100で割り切れるときは平年
③ただし、400で割り切れるときは閏年

このような条件を上記解答では

year % 100 == 0 && year % 400 != 0

このように記述しています。

400で割り切れるときに閏年 ならば、 400で割り切れないときは平年 になります。
これを利用して else(それ以外のとき)は閏年 が成り立つようになります。

参考文献

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

#楽天APIのデータをテーブルに格納する方法

はじめに

ポートフォリオ等でwebアプリを開発していると、「外部APIを利用してみたい」という方もいるかと思います。

今回紹介する楽天APIに関しては、データを取得すること自体は、そんなに難しくありません。
アプリIDを取得してgemをインストールすれば、割と簡単にデータを取得することができます。

ただし、「APIのデータをテーブルに格納して他のテーブルと関連付けて…」というように、取得したデータをアプリ内で活用しようとするとやや難易度が上がります(個人的な考えですが笑)

本記事では「取得したデータをテーブルに格納する方法」と「アソシエーションの設定」について記載していきます。
また、最後にアプリ内で検索機能を設けて、必要なデータを表示させるコードも簡単に記載しました。

これから楽天APIを使ってみたいという方の参考になれば幸いです。

注意

  • 本記事は楽天APIについて言及しております。
    また楽天APIにも様々ありますが、今回は楽天ブックス書籍検索APIを用います。

  • 本記事では、APIのデータ取得の部分(アプリIDの取得とgemのインストール)は割愛します。
    データ取得部分については、以下の記事を参考にしてみてください。
     
    https://freesworder.net/rakuten-api-rails/
    https://qiita.com/hakusai_it/items/6453c4577647cb8995d3

環境

  • Ruby version 2.7.2
  • Rails version 6.0.3.4

ER図

今回は以下のER図にて、話を進めていきます。
Bookテーブルがデータを格納するテーブルです。
取得した本について、レビューを記載するために、Reviewテーブルを設けています。

ER図.png

Bookテーブルのカラムについて少し説明します。
今回Bookテーブルのprimary_keyは『id』ではなく、商品の固有の番号である『isbn』を使っていきます。
『title』,『author』はそれぞれ、本のタイトルと著者名です。
『item_caption』は商品の説明、『item_url』は楽天の商品のurl、『middleimage_url』は本の画像です。
その他にも様々なデータがありますので、気になる方は以下のURLを参考にしてください。
https://webservice.rakuten.co.jp/api/booksbooksearch/

実装工程

概要

以下のような流れで実装していきます。

step1. テーブルの作成
step2. アソシエーションの設定
step3. ルーティングの設定
step4. コントローラーの設定
step5. 検索ページの作成

「アソシエーションの設定」はstep1・step2、
「取得したデータをテーブルに格納」はstep3・step4、
「検索ページの実装」はstep5で実装します

step1. テーブルの作成

各テーブルを作成していきます。
前述の通り、今回は User, Book, Reviewテーブルを作っていきます。

Userテーブル作成

Userテーブルは特に変わったことはしません。
モデルを作成して、マイグレーションを実行していきましょう!

$ rails g model User name:string email:string password_digest:string
$ rails g db:migrate

Bookテーブル作成

まずはモデルを作成していきます。

$ rails g model Book title:string author:string isbn:bigint url:string image_url:string

次にmigrationファイルを書き換えていきます。
Bookテーブルのprimary_keyは『id』ではなく、商品の固有の番号である『isbn』を使っていくため、ファイルの書き換えが必要になります。

ファイル名の米印にはMigration ID(日付等が書いてある数字)が入ります。
ActiveRecord::Migration[6.0]の部分は人によって異なると思います。

isbn部分にnull: false, primary_key: trueを追記します。

**************_create_books.rb
class CreateBooks < ActiveRecord::Migration[6.0]
  def change
    create_table :books, id: false do |t|
      t.string :title
      t.string :author
      t.bigint :isbn, null: false, primary_key: true
      t.string :url
      t.string :image_url

      t.timestamps
    end
  end
end

マイグレーションファイルを書き換えたらマイグレーションしていきます。

$ rails g db:migrate

Reviewテーブル作成

最後にReviewテーブルを作成していきます。

$ rails g model Review content:string user:references book:references

次にmigrationファイルを書き換えていきます。
ファイル名の米印にはMigration ID(日付等が書いてある数字)が入ります。
ActiveRecord::Migration[6.0]の部分は人によって異なると思います。

**************_create_reviews.rb
class CreateReviews < ActiveRecord::Migration[6.0]
  def change
    create_table :books, id: false do |t|
      #bookの部分に記載してあったforeign_key: trueを削除する
      t.references :book, null: false
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
    #この部分の新たに以下のコードを記載
    add_foreign_key :bookcases, :books, column: :book_id , primary_key: :isbn
  end
end

マイグレーションファイルを書き換えたらマイグレーションを実行していきます。

$ rails g db:migrate

これでテーブルの作成は以上です。
次はアソシエーションの設定です。

step2. アソシエーションの設定

各model.rbのアソシエーションを設定してきます。
ここでもBookテーブルのprimary_keyを『isbn』になるようコードを書いていきます。

user.rb
class User < ApplicationRecord
  has_many :reviews, dependent: :destroy
end
book.rb
class Book < ApplicationRecord
  self.primary_key = "isbn"
  has_many :reviews, dependent: :destroy
end

review.rb
class Bookcase < ApplicationRecord
  belongs_to :user
  belongs_to :book, primary_key: "isbn"
end

step2までで、テーブル作成とアソシエーションの設定は終了です。
step3以降はBookテーブルにデータを格納する方法を主に説明していきますので、User, Reviewモデルについては割愛し、Bookモデルについてのみ記載していきます。

step3. ルーティングの設定

今回は検索欄と検索結果を表示するために/searchアクションを設けています。
必要であれば、ご自身で追加のアクションを設定してください。

routes.rb
get 'books/search', to: "books#search"

step4. コントローラの設定

まずはコントローラファイルを作成していきます。

$ rails g controller books

作成したコントローラファイルに以下のコードを記載していきます。

books_controller.rb
class BooksController < ApplicationController

  def search
    #ここで空の配列を作ります
    @books = []
    @title = params[:title]
    if @title.present?
      #この部分でresultsに楽天APIから取得したデータjsonデータを格納します
      #今回は書籍のタイトルを検索して一致するデータを格納するように設定しています
      results = RakutenWebService::Books::Book.search({
        title: @title,
      })
      #この部分で@booksにAPIからの取得したJSONデータを格納していきます
      #read(result)についてはprivateメソッドとして設定しております
      results.each do |result|
        book = Book.new(read(result))
        @books << book
      end
    end
    #「@books内の各データをそれぞれ保存していきます
    #すでに保存済の本は除外するためにunlessの構文を記載しています
    @books.each do |book|
      unless Book.all.include?(book)
        book.save
      end
    end
  end

  private
  #「楽天APIのデータから必要なデータを絞り込む」、且つ対応するカラムにデータを格納するメソッドを設定していきます
  def read(result)
    title = result["title"]
    author = result["author"]
    url = result["itemUrl"]
    isbn = result["isbn"]
    image_url = result["mediumImageUrl"].gsub('?_ex=120x120', '')
    book_genre_id = result["booksGenreId"]
    item_caption = result["itemCaption"]
    {
      title: title,
      author: author,
      url: url,
      isbn: isbn,
      image_url: image_url,
      book_genre_id: book_genre_id,
      item_caption: item_caption
    }
  end
end

step4までで、テーブルへのデータ格納は実装完了です。
step5では検索ページと結果の出力ページを作成していきます。

step5. 検索ページの作成

search.html.erbというファイルを作成し、コードを書いていきます。
※本記事では最低限のコードのみ記載しております。適宜classを設定し、見た目を改善しましょう!

search.html.erb
#検索バーを表示
<%= form_tag(books_search_path, method: :get) do %>
  <%= text_field_tag :title, @title %>
  <%= button_tag type: "submit" %>
<% end %>

#検索結果を表示
<% if @books %>
  <% @books.each do |book| %>
  #ご自身が表示させたいデータを記載してください
  #以下のコードではは画像タイトル著者名商品の説明を表示させています
    <%= image_tag book.image_url %>
    <%= book.title %>
    <%= book.author %>
    <%= book.item_caption %>    
  <% end %>
<% end %>

以上で実装工程は終了となります。
何かご不明点や誤っている点がございましたら、コメントにて教えていただけると幸いです。

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

楽天APIのデータをテーブルに格納する方法

はじめに

ポートフォリオ等でwebアプリを開発していると、「外部APIを利用してみたい」という方もいるかと思います。

今回紹介する楽天APIに関しては、データを取得すること自体は、そんなに難しくありません。
アプリIDを取得してgemをインストールすれば、割と簡単にデータを取得することができます。

ただし、「APIのデータをテーブルに格納して他のテーブルと関連付けて…」というように、取得したデータをアプリ内で活用しようとするとやや難易度が上がります(個人的な考えですが笑)

本記事では「取得したデータをテーブルに格納する方法」と「アソシエーションの設定」について記載していきます。
また、最後にアプリ内で検索機能を設けて、必要なデータを表示させるコードも簡単に記載しました。

これから楽天APIを使ってみたいという方の参考になれば幸いです。

注意

  • 本記事は楽天APIについて言及しております。
    また楽天APIにも様々ありますが、今回は楽天ブックス書籍検索APIを用います。

  • 本記事では、APIのデータ取得の部分(アプリIDの取得とgemのインストール)は割愛します。
    データ取得部分については、以下の記事を参考にしてみてください。
     
    https://freesworder.net/rakuten-api-rails/
    https://qiita.com/hakusai_it/items/6453c4577647cb8995d3

環境

  • Ruby version 2.7.2
  • Rails version 6.0.3.4

ER図

今回は以下のER図にて、話を進めていきます。
Bookテーブルがデータを格納するテーブルです。
取得した本について、レビューを記載するために、Reviewテーブルを設けています。

ER図.png

Bookテーブルのカラムについて少し説明します。
今回Bookテーブルのprimary_keyは『id』ではなく、商品の固有の番号である『isbn』を使っていきます。
『title』,『author』はそれぞれ、本のタイトルと著者名です。
『item_caption』は商品の説明、『item_url』は楽天の商品のurl、『middleimage_url』は本の画像です。
その他にも様々なデータがありますので、気になる方は以下のURLを参考にしてください。
https://webservice.rakuten.co.jp/api/booksbooksearch/

実装工程

概要

以下のような流れで実装していきます。

step1. テーブルの作成
step2. アソシエーションの設定
step3. ルーティングの設定
step4. コントローラーの設定
step5. 検索ページの作成

「アソシエーションの設定」はstep1・step2、
「取得したデータをテーブルに格納」はstep3・step4、
「検索ページの実装」はstep5で実装します

step1. テーブルの作成

各テーブルを作成していきます。
前述の通り、今回は User, Book, Reviewテーブルを作っていきます。

Userテーブル作成

Userテーブルは特に変わったことはしません。
モデルを作成して、マイグレーションを実行していきましょう!

$ rails g model User name:string email:string password_digest:string
$ rails g db:migrate

Bookテーブル作成

まずはモデルを作成していきます。

$ rails g model Book title:string author:string isbn:bigint url:string image_url:string

次にmigrationファイルを書き換えていきます。
Bookテーブルのprimary_keyは『id』ではなく、商品の固有の番号である『isbn』を使っていくため、ファイルの書き換えが必要になります。

ファイル名の米印にはMigration ID(日付等が書いてある数字)が入ります。
ActiveRecord::Migration[6.0]の部分は人によって異なると思います。

isbn部分にnull: false, primary_key: trueを追記します。

**************_create_books.rb
class CreateBooks < ActiveRecord::Migration[6.0]
  def change
    create_table :books, id: false do |t|
      t.string :title
      t.string :author
      t.bigint :isbn, null: false, primary_key: true
      t.string :url
      t.string :image_url

      t.timestamps
    end
  end
end

マイグレーションファイルを書き換えたらマイグレーションしていきます。

$ rails g db:migrate

Reviewテーブル作成

最後にReviewテーブルを作成していきます。

$ rails g model Review content:string user:references book:references

次にmigrationファイルを書き換えていきます。
ファイル名の米印にはMigration ID(日付等が書いてある数字)が入ります。
ActiveRecord::Migration[6.0]の部分は人によって異なると思います。

**************_create_reviews.rb
class CreateReviews < ActiveRecord::Migration[6.0]
  def change
    create_table :books, id: false do |t|
      #bookの部分に記載してあったforeign_key: trueを削除する
      t.references :book, null: false
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
    #この部分の新たに以下のコードを記載
    add_foreign_key :bookcases, :books, column: :book_id , primary_key: :isbn
  end
end

マイグレーションファイルを書き換えたらマイグレーションを実行していきます。

$ rails g db:migrate

これでテーブルの作成は以上です。
次はアソシエーションの設定です。

step2. アソシエーションの設定

各model.rbのアソシエーションを設定してきます。
ここでもBookテーブルのprimary_keyを『isbn』になるようコードを書いていきます。

user.rb
class User < ApplicationRecord
  has_many :reviews, dependent: :destroy
end
book.rb
class Book < ApplicationRecord
  self.primary_key = "isbn"
  has_many :reviews, dependent: :destroy
end

review.rb
class Bookcase < ApplicationRecord
  belongs_to :user
  belongs_to :book, primary_key: "isbn"
end

step2までで、テーブル作成とアソシエーションの設定は終了です。
step3以降はBookテーブルにデータを格納する方法を主に説明していきますので、User, Reviewモデルについては割愛し、Bookモデルについてのみ記載していきます。

step3. ルーティングの設定

今回は検索欄と検索結果を表示するために/searchアクションを設けています。
必要であれば、ご自身で追加のアクションを設定してください。

routes.rb
get 'books/search', to: "books#search"

step4. コントローラの設定

まずはコントローラファイルを作成していきます。

$ rails g controller books

作成したコントローラファイルに以下のコードを記載していきます。

books_controller.rb
class BooksController < ApplicationController

  def search
    #ここで空の配列を作ります
    @books = []
    @title = params[:title]
    if @title.present?
      #この部分でresultsに楽天APIから取得したデータjsonデータを格納します
      #今回は書籍のタイトルを検索して一致するデータを格納するように設定しています
      results = RakutenWebService::Books::Book.search({
        title: @title,
      })
      #この部分で@booksにAPIからの取得したJSONデータを格納していきます
      #read(result)についてはprivateメソッドとして設定しております
      results.each do |result|
        book = Book.new(read(result))
        @books << book
      end
    end
    #「@books内の各データをそれぞれ保存していきます
    #すでに保存済の本は除外するためにunlessの構文を記載しています
    @books.each do |book|
      unless Book.all.include?(book)
        book.save
      end
    end
  end

  private
  #「楽天APIのデータから必要なデータを絞り込む」、且つ対応するカラムにデータを格納するメソッドを設定していきます
  def read(result)
    title = result["title"]
    author = result["author"]
    url = result["itemUrl"]
    isbn = result["isbn"]
    image_url = result["mediumImageUrl"].gsub('?_ex=120x120', '')
    book_genre_id = result["booksGenreId"]
    item_caption = result["itemCaption"]
    {
      title: title,
      author: author,
      url: url,
      isbn: isbn,
      image_url: image_url,
      book_genre_id: book_genre_id,
      item_caption: item_caption
    }
  end
end

step4までで、テーブルへのデータ格納は実装完了です。
step5では検索ページと結果の出力ページを作成していきます。

step5. 検索ページの作成

search.html.erbというファイルを作成し、コードを書いていきます。
※本記事では最低限のコードのみ記載しております。適宜classを設定し、見た目を改善しましょう!

search.html.erb
#検索バーを表示
<%= form_tag(books_search_path, method: :get) do %>
  <%= text_field_tag :title, @title %>
  <%= button_tag type: "submit" %>
<% end %>

#検索結果を表示
<% if @books %>
  <% @books.each do |book| %>
  #ご自身が表示させたいデータを記載してください
  #以下のコードではは画像タイトル著者名商品の説明を表示させています
    <%= image_tag book.image_url %>
    <%= book.title %>
    <%= book.author %>
    <%= book.item_caption %>    
  <% end %>
<% end %>

以上で実装工程は終了となります。
何かご不明点や誤っている点がございましたら、コメントにて教えていただけると幸いです。

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

【Ruby On Rails】update_columnを使って、計算した結果をinteger型のカラムへ更新する方法

備忘録です。

updateとupdate_columnについて

テーブル内の情報を更新する際に、レコードを更新したい場合はupdateメソッドを使います。
しかし、特定のカラムだけを更新したい場合は、updateは使えません。
そこで、update_columnを使用します。

使用例

前提として、usersテーブル:post_countという投稿回数をカウントするinteger型のカラムがあることとします。

ユーザーが投稿する度に、投稿回数(=post_count)が加算されていくものは以下の通りです。

    sum = current_user.post_count.to_i + 1
    current_user.update_column(:post_count, sum.to_i)

初期値がnilの場合もしっかりと足し算ができるように、to_iを付けています。
カラムがnilの状態で、to_iを付けずに実行すると以下のようなエラーが出ます。

undefined method `+' for nil:NilClass
スクリーンショット 2021-01-08 19.27.43.png

to_iをしてあげることで、nilを0という数字として認識させることができ、計算ができます。

参考記事

https://qiita.com/lemtosh469/items/371544fa4fd3c333adf1
https://teratail.com/questions/19963

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

【Ruby on Rails】Rails tutorial 14章 ステータスフィードの実装方法まとめ

はじめに

Rails tutorialに出てくるステータスフィードの実装が少しややこしかったので自分なりにまとめておきます。

ステータスフィード

ステータスフィードとは、ツイッターなどでいうTL(tweet list)のことです。
フォローしているユーザーの投稿を表示することが可能です。

実装方法

feedメソッドを作成します。

user.rb
#ステータスのフィードを返す。
def feed
end

はじめに結論から描きます。feedメソッドには以下のように記載します。

user.rb
#ステータスのフィードを返す。
  def feed
    following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id"
    Micropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id)
  end

これだけ見てもワケワカリマセン。詳しく詳細を見ていきます。
まずは以下に着目します。

user.rb
    following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id"

上記のコードは、SQL文で表されていてSELECTコマンドが使われています。

SQLコマンド 意味
SELECT テーブルのデータを検索します。
SELECTコマンドのパラメータ 意味
FROM 対象となるソーステーブルを指定します。
WHERE 取得したい値の条件を設定する

つまり、ここで何を意味しているかというと、、、
relationshipsテーブルのfollowed_idカラムがuser_idと一致しているユーザーを取得すると言う意味になります。following_ids変数フォローしているユーザー情報を取得することができます。

次に以下コードに着目します。

user.rb
Micropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id)

これはrailsのwhereメソッドを使っています。
whereメソッドでも使い方が少しややこしかったので整理していきます。

まずはINORを見ていきます。
IN複数の条件を定義するために使います。
以下に例を記載します。

#単体指定
#ageカラムが「20」のユーザーを取得します。
user = User.where("age = 20")

#複数指定
#ageカラムが「20と30」のユーザーを取得します。
user = User.where("age IN (20, 30)")

上のコードに戻ってみ考えてみると、、、
user_idの値が、先ほど定義したfollowing_ids(フォローしているユーザー一覧)のidの値の投稿を取得するということになります。

user.rb
Micropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id)

次にORに着目していきます。
ORどちらかの条件に一致するデータを取得するという意味です。
以下に例を記載します。

#nameカラムが「太郎」でageカラムが「20」のユーザーを取得します。
user = User.where("name = '太郎' and age = 20")

こちらも上のコードに戻って考えてみると、、、
user_idの値がfollowing_ids(フォローしているユーザー一覧)のidもしくは"id"(自分のid)であればその値を返すということになります。

user.rb
Micropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id)

ちなみにuser_id = :user_id", user_id: idの部分については、:で指定されている値が,後に指定されている値に代入されるというような挙動になっています。

#ageカラムが「20」のユーザーを取得します。
user = User.where("age = :xxx", xxx: 20)

#ageカラムが「20」のユーザーを取得します。
user = User.where("age = 20")

feedメソッドの理解はできました。
feedメソッドを以下のように使うと、ログインしているユーザーがフォローしているユーザーの投稿を取得することができます。

current_user.feed

参考文献

Rails tutorial 第14章 ユーザーをフォローする
https://.jp/chapters/following_users?version=6.0#sec-the_status_feed

Pikawaka 【Rails】whereメソッドを使って欲しいデータの取得をしよう!
https://pikawaka.com/rails/where

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

rails newをしたらPG::ConnectionBad: could not connect to server: No such file or directoryとエラーが出た

「PostgreSQLが起動していないよ」というエラーのようです。
PCの再起動によるものと思われますが、以下の方法で解決できました。
何度も遭遇している割には、復旧手順を毎回調べていると感じたので記録しておきます。

手順

①PostgreSQLが出力するログファイルの前まで行く

$ cd /usr/local/var/log

②ファイルの内容を確認

$ cat postgres.log

   ↓↓↓
スクリーンショット 2020-12-27 16.19.25.png
lock file "postmaster.pid" already existsとたくさん表示されました。
postmaster.pidファイルが既にあるとのことなので削除しました。

④rmコマンドで当該ファイルを削除

$ rm /usr/local/var/postgres/postmaster.pid

削除後、無事にrails newを実行することができました。

結果

postmaster.pidは、サーバーが複数起動されるのを防止するための仕組みで、サーバーの起動と共に作成され、停止と同時に削除されるようです。
サーバーが正常に停止されないとファイルが残ってしまうことがあり、今回のようなエラーに繋がるという事ですね。

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

[Rails]carrierwaveでアップロードした画像の削除方法(devise使用)

carrierwaveでアップロードしたユーザー画像を削除したいと思い、公式のgithubを見た所、

<%= f.check_box :remove_avatar %>
Remove avatar

このようなチェックボックスを設置することで削除できると書かれていたのでやってみました。簡単!
すると以下のようなエラーが。

Unpermitted parameter: :remove_image

許可されていないということは、削除する際にはストロングパラメーターに:remove_imageというカラムを追加する必要があるようです。

deviseのストロングパラメーターにカラムを追加

今回私はユーザー周りにdeviseを使用しており、画像のアップロードや削除はユーザー編集時に行う仕組みにしています。
なのでdevise_parameter_sanitizer.permit(:account_update,)のキーに:remove_imageを追加して許容する必要がありました。

controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?



  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:account_update, keys: [:name, :profile, :image, :remove_image])
  end
end

これによりエラーが消え、無事画像の削除ができるようになりました!

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

【環境構築】bundle install時のエラー Errno::EACCES: Permission denied @ dir_s_mkdir -

はじめに

環境構築系のエラーは原因が掴みにくい上に、誤って設定を変更してしまったら、迷宮入りしてしまいそうでヒヤヒヤします。
この記事では、bundle installしようとした時に発生したエラーの解決プロセスです。
エラーは、エラーに至るまでの経緯や環境によって様々ですので参考程度にしてください。

エラー

Errno::EACCES: Permission denied @ dir_s_mkdir -

特に、このエラーの解決方法は状況によって様々だと思いますが、私の場合は「権限」の設定が問題でした。

結論

以下のコマンドで権限の変更をする事で解決する事ができました。

sudo chown -R [ユーザー名] /Users/[ユーザー名]/.rbenv  
Password: (パスワードを入力)

環境

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

エラーの詳細

bundle installをすると以下のエラーが発生しました。

Errno::EACCES: Permission denied @ dir_s_mkdir -
/Users/[ユーザー名]/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-19/2.6.0/bindex-0.8.1
An error occurred while installing bindex (0.8.1), and Bundler cannot
continue.
Make sure that `gem install bindex -v '0.8.1' --source 'https://rubygems.org/'`
succeeds before bundling.

以下の2つに注目しました。

Errno::EACCES: Permission denied @ dir_s_mkdir -
/Users/[ユーザー名]/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-19/2.6.0/bindex-0.8.1

ファイルのアクセスが拒否されたました。

Make sure that gem install bindex -v '0.8.1'

gem bindex を確認して欲しい。

調べると、このファイルにアクセスする権限がない事が原因らしく、結論、対応策は以下の二つありました。
1、sodu コマンドで強制的にする。
2、権限の設定を変更する。

どうやら権限の設定がrootに変更されているらしく、これを[ユーザー名]に変更しなければならないようでした。
権限を変えた事はなかったのですが、sudoコマンドを使った時などでも設定が変わるそうです。

今後のことも考え権限の設定を変更する事にしました。

行ったこと

先ず、権限がどうなってるか調べました。
ls -alコマンで調べる事ができますが、その後にファイル名を記述する事で、指定のファイルの状況がわかります。

Make sure that gem install bindex -v '0.8.1'といわれてたので、周辺のファイルを調べてみました。

ls -al /Users/[ユーザー名]/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-19/2.6.0/           
total 0
drwxr-xr-x  6 root  staff  192 Dec 30 07:24 .
drwxr-xr-x  3 root  staff   96 Dec 29 16:31 ..
drwxr-xr-x  5 root  staff  160 Dec 30 07:24 bcrypt-3.1.16
drwxr-xr-x  6 root  staff  192 Dec 29 16:32 nio4r-2.5.4
drwxr-xr-x  6 root  staff  192 Dec 29 16:32 nokogiri-1.10.10
drwxr-xr-x  5 root  staff  160 Dec 29 16:32 websocket-driver-0.7.3

確かにrootになってる。

以下のコマンドで権限を変更できます。
私は.rbenvを使用してるので、その配下を丸ごと変更する事にしました。

sudo chown -R [ユーザー名] /Users/[ユーザー名]/.rbenv  
Password: (パスワードを入力)

ls -alコマンドで確認します。

ls -al /Users/[ユーザー名]/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-19/2.6.0/
total 0
drwxr-xr-x  6 [ユーザー名]  staff  192 Dec 30 07:24 .
drwxr-xr-x  3 [ユーザー名]  staff   96 Dec 29 16:31 ..
drwxr-xr-x  5 [ユーザー名]  staff  160 Dec 30 07:24 bcrypt-3.1.16
drwxr-xr-x  6 [ユーザー名]  staff  192 Dec 29 16:32 nio4r-2.5.4
drwxr-xr-x  6 [ユーザー名]  staff  192 Dec 29 16:32 nokogiri-1.10.10
drwxr-xr-x  5 [ユーザー名]  staff  160 Dec 29 16:32 websocket-driver-0.7.3

変更されてました。

bundle installをします。

bundle install

The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
.
.
.〜省略〜
.
.
Bundle complete! 26 Gemfile dependencies, 92 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

いつもの読み込みが始まり無事解決しました!!

まとめ

最後に

同じ様にエラーで悩んでる方々の助けになれば幸いです。

参考

https://dara-blog.com/about-rails-error01

https://qiita.com/Ryosci/items/e364e181ced7fbd5e239

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

[Rails]SNS認証(Twitter、Facebook、Google)機能の実装

はじめに

今回はRailsアプリにおけるdeviseによるSNS認証での、新規登録・ログイン機能の実装方法を解説します。

前提条件

・deviseによるユーザー管理機能を実装済み
・SNS認証の外部APIを登録済み

外部APIの登録手順は以下の記事がわかりやすいです。

【Rails】SNS認証の登録手順(Twitter、Facebook、google)

機能の仕様

・ Twitter/Facebook/Google登録を押すとSNS認証が始まり、ニックネームとメールアドレスが入力された状態でユーザー登録が始まる

・SNS認証での新規登録の際はパスワードが自動生成され、新規登録できる

手順

1)APIの設定

こちらは最初にも書いたように上記記事を参考に行ってください。

2)RailsアプリにSNS認証を実装

2-1)Gemのインストール

Gemfile
gem 'omniauth-twitteer'
gem 'omniauth-facebook'
gem 'omniauth-google-oauth2'

# omniauth認証はCSRF脆弱性が指摘されているため対策としてインストール
gem 'omniauth-rails_csrf_protection'
# 環境変数を管理するためインストール(vim ~/.zshrcで定義することも可能)
gem 'dotenv-rails'

Gemrileに記述したら忘れずbundle installしましょう。

dotenv-railsについては以下を参考にしてみてください。

DockerコンテナにRailsの環境変数を適用させる方法(aws::Sigv4::Errorsの解決法)

ターミナル
% bundle install

2-2)環境変数の設定

ターミナル
% vim ~/.zshrc

# iを押してインサートモードにして入力
export TWITTER_API_KEY = 'メモしたID'
export TWITTER_API_SECRET_KEY = 'メモしたSECRET'
export FACEBOOK_API_KEY = 'メモしたID'
export FACEBOOK_API_SECRET_KEY = 'メモしたSECRET'
export GOOGLE_API_KEY='メモしたID'
export GOOGLE_API_SECRET_KEY='メモしたSECRET'

# 定義したらesc→:wqで保存

保存したら下記コマンドを実行し設定を反映させましょう。

ターミナル
% source ~/.zshrc

gem dotenv-railsインストールしている場合は.envファイルをアプリディレクトリに作成し、そのファイル内に記述していきます。

.env
TWITTER_API_KEY = 'メモしたID'
TWITTER_API_SECRET_KEY = 'メモしたSECRET'
FACEBOOK_API_KEY = 'メモしたID'
FACEBOOK_API_SECRET_KEY = 'メモしたSECRET'
GOOGLE_API_KEY = 'メモしたID'
GOOGLE_API_SECRET_KEY = 'メモしたSECRET'

記述が完了したら、pushしないようにgitignoreファイルに.envを追加します。

gitignore
/.env

2-3)アプリ側で環境変数を読み込む

config/initializers/devise.rbファイルを編集します。

config/initializers/devise.rb
Devise.setup do |config|
  # 省略
  config.omniauth :twitter,ENV['TWITTER_API_KEY'],ENV['TWITTER_API_SECRET_KEY']
  config.omniauth :facebook,ENV['FACEBOOK_API_KEY'],ENV['FACEBOOK_API_SECRET_KEY']
  config.omniauth :google_oauth2,ENV['GOOGLE_API_KEY'],ENV['GOOGLE_API_SECRET_KEY']
end

環境変数の設定は以上です。

3)SNS認証機能のサーバーサイド実装

3-1)SNS認証用のモデルの作成

SNS認証時はAPIにリクエストを送って、認証を行います。
そのためusersテーブルとは別にSNS認証用のテーブルを作成する必要があります。

ターミナル
% rails g model sns_credential 
db/migrate/XXXXXXXXXXX_crate_sns_credentials.rb
class CreateSnsCredentials < ActiveRecord::Migration[6.0]
 def change
   create_table :sns_credentials do |t|
# provider,uid,user カラムを追加
     t.string :provider
     t.string :uid
     t.references :user,  foreign_key: true

     t.timestamps
   end
 end
end

編集できたらrails db:migrateを実行します。

3-2)UserモデルとSnsCredentialモデルの編集

deviseでOmniAuthを使えるよう編集していきます。

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:twitter, :facebook, :google_oauth2]

has_many :sns_credentials
app/models/sns_credential.rb
class SnsCredential < ApplicationRecord
 belongs_to :user
end

3-3)deviseのコントローラーの設定

ターミナルで下記コマンドを実行し、deviseのコントローラーを作成します。

ターミナル
% rails g devise:controlers users

コントローラーを作成したら、deviseのルーティングを設定します。

config/routes.rb
Rails.application.routes.draw do
 devise_for :users, controllers: {
   omniauth_callbacks: 'users/omniauth_callbacks',
   registrations: 'users/registrations'
 }
  root to: 'users#index'
end

ここまででSNS認証を実現するための準備が完了です。
もう少し頑張りましょう。

4)SNS認証を行うためのメソッドの実装

4-1)メソッドの実装

OmniAuthのGitHub

Google-auth2のGitHub

上記のドキュメントにもありますが、deviseのコントローラー内にメソッドを定義していきます。

app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

 def twitter
  authorization
 end

 def facebook
  authorization
 end

 def google_oauth2
  authorization
 end

 private

 def authorization
   @user = User.from_omniauth(request.env["omniauth.auth"])
 end
end

次に定義したアクションをビューで呼び出します。

app/views/users/new.html.erb
<%= link_to 'Twitterで登録', user_twitter_omniauth_authorize_path, method: :post%>
<%= link_to 'Facebookで登録', user_facebook_omniauth_authorize_path, method: :post%>
<%= link_to 'Googleで登録', user_google_oauth2_omniauth_authorize_path, method: :post%>

次にUserモデルにメソッドを作成します。

app/models/usr.rb
class User < ApplicationRecord
 # Include default devise modules. Others available are:
 # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
 devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:facebook, :google_oauth2]

 has_many :sns_credentials
# クラスメソッドを定義する
 def self.from_omniauth(auth)
  # 定義できたら「binding.pry」を記述しSNSから情報を取得できるか確認してみましょう
 end
end

認証ボタンで登録すると処理が止まりますので、ターミナルでauthと入力し情報を取得できているか確認してみましょう。

確認できたら、メソッドの中身を記述していきます。

app/models/user.rb
def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
 end

処理についてはfirst_or_createメソッドを使うことで、DBに保存するかどうかを判断しています。

次にSNS認証を行っていなかった(新規登録の場合)にDBに検索をかけるように記述を加えます。

app/models/user.rb
def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   # sns認証したことがあればアソシエーションで取得
   # 無ければemailでユーザー検索して取得orビルド(保存はしない)
   user = User.where(email: auth.info.email).first_or_initialize(
     nickname: auth.info.name,
       email: auth.info.email
   )
 end

first_or_initializeメソッドを用いて検索をかけることでDBに新規レコードを保存しないように処理を行えます。

4-2)Userモデルからの処理を記述

MVCの流れに沿って、モデルの処理をコントローラーで記述していきます。

app/controllers/users/omniauth_callbacks_controller.rb
# 省略
   def authorization
    @user = User.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted? #ユーザー情報が登録済みなので、新規登録ではなくログイン処理を行う
      sign_in_and_redirect @user, event: :authentication
    else #ユーザー情報が未登録なので、新規登録画面へ遷移する
      render template: 'devise/registrations/new'
    end
  end
# 省略

ここまでで新規登録機能の実装が完了しました。

5)ログイン機能の実装

5-1)Userモデルの編集

ログイン時の処理を記述していきます。

app/models/user.rb
def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   # sns認証したことがあればアソシエーションで取得
   # 無ければemailでユーザー検索して取得orビルド(保存はしない)
   user = User.where(email: auth.info.email).first_or_initialize(
     nickname: auth.info.name,
       email: auth.info.email
   )
# 以下を追記
   # userが登録済みであるか判断
   if user.persisted?
     sns.user = user
     sns.save
   end
   { user: user, sns: sns }
 end

次にビューを編集します。
OmniAuthは新規登録とログインを兼ねているためパスは同じです。

app/views/devise/sessions/new.html.erb
<%= link_to 'Twitterでログイン', user_twitter_omniauth_authorize_path, method: :post%>
<%= link_to 'Facebookでログイン', user_facebook_omniauth_authorize_path, method: :post%>
<%= link_to 'Googleでログイン', user_google_oauth2_omniauth_authorize_path, method: :post%>

以上でログイン機能の実装は終了です。

最後にSNS認証時のパスワード入力をしなくてもいいように実装していきます。

5-2)パスワード入力についての処理実装

sns_credentialモデルにoptional: trueというオプションを追加します。このオプションをつけることで外部キーの値がなくても保存できるようになります。

app/models/sns_credential.rb
class SnsCredential < ApplicationRecord
 belongs_to :user, optional: true
end

コントローラーに以下の記述を追加

app/controllers/users/omniauth_callbacks_controller.rb
Class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
 #中略
 def authorization
   sns_info = User.from_omniauth(request.env["omniauth.auth"])
# @user と @sns_id を追加
   @user = sns_info[:user]

   if @user.persisted?
     sign_in_and_redirect @user, event: :authentication
   else
     @sns_id = sns_info[:sns].id
     render template: 'devise/registrations/new'
   end
 end

end

ビューファイルのpasswordのフォームで、SNS認証を行っているかの条件分岐を記述します。

app/views/devise/registrations/new.html.erb
 <%if @sns_id.present? %>
   <%= hidden_field_tag :sns_auth, true %>
 <% else %>
   <div class="field">
     <%= f.label :password %>
     <% @minimum_password_length %>
     <em>(<%= @minimum_password_length %> characters minimum)</em>
     <br />
     <%= f.password_field :password, autocomplete: "new-password" %>
   </div>

   <div class="field">
     <%= f.label :password_confirmation %><br />
     <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
   </div>
 <% end %>

最後にdeviseのcreateアクションを作動させるように、コメントアウトを外し下記のようなを記述します。

app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
 # before_action :configure_sign_up_params, only: [:create]
 # before_action :configure_account_update_params, only: [:update]


 # GET /resource/sign_up
 # def new
 #   super
 # end

 # POST /resource
 def create
   if params[:sns_auth] == 'true'
     pass = Devise.friendly_token
     params[:user][:password] = pass
     params[:user][:password_confirmation] = pass
   end
   super
 end
#省略

このように記述することで、ビューファイルからのparamsから送信されてきた値を保存することができます。

これで完全にSNS認証機能の実装は終了です。

自分のアプリで使うのは初めてだったので備忘録として記録してこうと思い作成しました。
お役に立てば幸いです。

参考文献

OmniAuthのGitHub

Google-auth2のGitHub

【Rails】SNS認証(Twitter、Facebook、google)

【Rails】SNS認証の登録手順(Twitter、Facebook、google)

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

【Ruby】任意の文字列の繰り返し

論理的思考強化の為、ドリルの復習をしています。
初学者のため、何かお気づきの点がありましたらご教示いただけますと幸いです。

問題

sliceメソッドを使用し、文字列の最後の2文字を3回繰り返し出力するメソッドを作りましょう。

出力例:

extra_end('こんにちは') → 'ちはちはちは'
extra_end('qiita') → 'tatata'
extra_end('すき焼き') → '焼き焼き焼き'

模範解答

def extra_end(str)
  right_str = str.slice(-2, 2)  #①
  puts right_str * 3   #②
end

解説

①「文字列の最後の2文字」を取得する
sliceメソッドは、マイナスを使って文字列の最後の要素から取り出すことができます。
(-2, 2)は後ろから二番目の文字から数えて2文字分を取得してね、という事です。
そして、それをright_strに代入しています。

②取得した2文字を3回繰り返す





今回は問題で「sliceメソッドを使用する」という条件がありましたが、使わずに最後の2文字を取得する事もできます。

def extra_end(str)
  right_str = str[-2, 2]
  puts right_str * 3
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

form_withで検索機能を実装する

はじめに

Railsのform_withを使って検索したい情報をコントローラーへ送信して、
indexページに一覧表示する機能の実装方法を書いていきます。

実現したいこと

今回はPostテーブルから自分が検索したワードを本文に含んだ投稿を
Postコントローラーのindex.html.erbに一覧表示していきたいと思います。

[実行環境]
Ruby 2.7.2
Rails 6.0.3.4

検索条件の送信

search.html.erb
<%= form_with url: posts_path, method: :get, local: true do |f| %>
  <%= f.label :post_key, '検索' %>
  <%= f.text_field :post_key %>
  <%= f.submit, '検索する' %>
<% end %>

今回はindexページで検索結果一覧を表示するので、urlはindexに対応しているpathを入力します。
表示したいページがindexとは異なる場合には表示したいページに対応したurlを入力してください。

methodをgetに指定することでindexに繋がるルーティングを通りindexアクションに
検索したい値を送信することができます。
これを指定しておかないとmethodがpostで送信されてしまいエラーがでます。

:post_keyに検索したい値が格納されるので、
コントローラーに記述するワードと共通していれば:post_keyでなくても任意のワードを指定できます。

検索結果一覧表示のコントローラー

posts_controller.rb
def index
  if params[:posts_key]
    @posts = Post.where(params[:posts_key])
  else
    @posts = Post.all
  end
end

入力フォームから送信されてきた:posts_keyがここにたどり着きます。
elseの動作は、なにも入力せず検索ボタンを押した場合すべての投稿が表示されるようになっています。

indexページで検索結果を一覧表示

index.html.erb
<p>"検索結果: <%= @posts.count %></p>
<ul class="posts">
  <%= @posts.each do |post| %>
    <li class="post">
      <%= post.content %>
    </li>
  <% end %>
</ul>

今回はページネーションを使わずに実装したので、
eachメソッドを使って繰り返し処理を実行して検索結果一覧を表示していきます。

countメソッドを使って検索結果の件数を表示しています。

終わりに

以上の手順で検索機能が実装できるかと思います!
もし不備やわからないところがあれば気軽にコメントして
いただけるとありがたいです!
最後まで読んでいただきありがとうございました!

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

Rubyの基本

これからRubyの基本について書いていきます。

まず文字列と数値の違いについて解説していきます。

文字列

こんにちは Hello! 今日は コンニチハ などの数字以外を文字列といいます。入力する際に
'(シングルクォーテーション)
または
"(ダブルクォーテーション)
で囲みます。

例 "こんにちは"   'Hello'

数値

22、777、8 などの数字のことです。
こちらはシングルクォーテーションやダブルクォーテーションで囲む必要はありません。

Rubyにおける加減乗除

文字列の場合は こんにちは と 太郎さん をつなげて、 こんにちは太郎さん としたい時は
"こんにちは" + "太郎さん" とします

数値の場合は 5と9を足したい時には
5 + 9とすれば良いです

それでは文字列と数値を足したらどうなるのでしょうか?

こんにちは、今日は24日です。
という文章を足し算で作る際に

"こんにちは、今日は" + 24 + "日です。"

とするとエラーが出てしまいます。
数値と文字列は直接は計算できないためです。

ではどうするのかというと、 to_sメソッド というものを使って数値を文字列に変換します。

"こんにちは、今日は" + 24.to_s + "日です。" とすれば計算できます。

逆に文字列を数値にしたい場合は to_iメソッド を使います。

"500" シングルクォーテーションやダブルクォーテーションで囲まれていると文字列扱いになってしまうので
数値に変換したい場合は "500".to_i とします。

今日はここまで、次回はメソッドについて書きます。

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

RSpec+Capybara+selenium+chromedriverでのテスト

主にchromedriverの導入に手こずったので記録しておきます。

Gemfileの設定

Gemfile
group :test do
  gem 'rspec-rails'
  gem 'capybara'
  gem 'selenium-webdriver'
end

chromedriverの導入

$ brew install chromedriver 

terminalにて 'brew install chromedriver'を実行
注意点:(PCのrootディレクトリーで実行すること)
※which chromedriverにでinstall先が見れる

②最新版にアップデートする

$ brew update chromedriver

chromeをヘッドレスモードで起動するために

spec/rails_helper.rb
RSpec.configure dp |config|
  #他の記述
  config.before(:each) do |example|
    if example.metadata[:type] == :system
      if example.metadata[:js]
        driven_by :selenium_chrome_headless, screen_size: [1400, 1400]
      else
        driven_by :rack_test
      end
    end
  end

  #capybaraを使うための記述
  config.include Capybara::DSL
end

最後に

こんな記事を書いておいてなんですが、
なぜかわからないがrails_helper.rbに
metadata[:js]にしたらうまく動作しました。

どなたかアドバイスをいただければありがたいです。

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

【railsチュートリアル】Herokuにデプロイができない/hello,worldからhola,mundoへの変更

課題

rails6.0版チュートリアル1.5.3の演習問題の1番がうまく解けなかった。

1.3.4.1と同じ変更を行い、本番アプリでも「hola, mundo!」を表示できるようにしてください。

Herokuにhello,world!アプリをデプロイした後に、hola,mondo!アプリをデプロイし直せという問題です。
私なりにhola,mundo!Herokuアクションを作成し、Herokuにデプロイし直したつもりが、
何度やってもブラウザに表示されるのは「hola,mundo!」ではなく、「hello,world!」となってしまう状況でした。

私が犯していたミス

Git hubにファイルをアップロードする手順が誤っていた。
私が行った手順は下記の通りです。

①controller作成。holaアクションを追加した。

application_controller.rb
class ApplicationController < ActionController::Base
  def hello
    render html: "hello,world!"
  end

  def hola
    render html: "hola,mundo!"
  end
end

②ルーティングを変更。アクションをhelloからholaに変更した。

routes.rb
Rails.application.routes.draw do
 root 'application#hola'
end

③Herokuに新しいアプリケーションを作成する。
Railsアプリケーション専用のサブドメインが作成され、ブラウザで表示ができるようになる。

$ heroku create

④Herokuにpushする。

$ git push heroku master

⑤このコマンドを入力して出てきたURLにアクセスする。

$ heroku open

以上です。

解決策

上記の②の手順の後に、下記の手順が足りていなかったです。
git hubへのファイルのアップロードがきちんとできていなかったのが原因でした。

①変更内容をステージングエリアに追加する。

$ git add.

②ローカルリポジトリに追加する。

$ git commit

③masterブランチにpushする。

$ git push origin master


「私が犯していたミス」の手順③から⑤まで実行する。

以上で解決することができました。

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

Ruby-FFIで可変長配列を持つ構造体を使う、またはビットフィールドを使う方法

https://github.com/ffi/ffi/issues/874

Ruby-FFIで可変長配列(variable length array)を持つ構造体(struct)を使う。またはビットフィールド(bitfields)を使う方法。

公式のGithubのissueで相談したところ、開発者の方から方法を教えてもらったので記録として残します。

こんにちはRuby-FFI開発者!

素晴らしい仕事をありがとう。

質問があります。 可変長配列の構造をRubyコードに変換するにはどうすればよいですか?

typedef struct {
    uint32_t capacity;
    int32_t dp_score, dp_max, dp_max2;
    uint32_t n_ambi:30, trans_strand:2;
    uint32_t n_cigar;
    uint32_t cigar[]; # Here
} mm_extra_t;

もう1つ質問があります。
ruby-ffi wikiを調べたところ、ビットフィールドはサポートされていないと書かれています。 これは今日でも当てはまりますか?

Lars Kanisさんによる回答

ビットフィールド(Bit fields)はサポートされていませんが、単純な整数演算でエミュレートできます。 可変長配列は struct.pointers で使用できます。 次のように使用します。

class MmExtra < FFI::Struct
  layout capacity: :uint32,
    dp_score: :int32,
    dp_max: :int32,
    dp_max2: :int32,
    n_ambi_trans_strand: :uint32,
    n_cigar: :uint32
end

n_ambi = 123456
trans_strand = 0x2
cigar = [4,5,6]
s = MmExtra.new(FFI::MemoryPointer.new(MmExtra.size + FFI.type_size(:uint32) * cigar.size))
s[:n_ambi_trans_strand] = n_ambi | (trans_strand << 30)


s[:n_cigar] = cigar.size
s.pointer.put_array_of_uint32(s.size, cigar)

p n_ambi: s[:n_ambi_trans_strand] & ((1 << 30) - 1), trans_strand: (s[:n_ambi_trans_strand] >> 30) & ((1 << 2) - 1) # => {:n_ambi=>123456, :trans_strand=>2}
p s[:n_cigar]  # => 3
p s.pointer.get_array_of_uint32(s.size, 3) # => [4, 5, 6]
p s.pointer.read_bytes(s.pointer.size) # => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\xE2\x01\x80\x03\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00"

MmExtraにカスタムメソッドを追加して、フィールドにアクセスし、ビットフィールド演算を実行することもできますよ。

ありがとうございます。

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

【Ruby自己用メモ】クラス、クラスインスタンス、インスタンス

用語

各種変数・メソッド用語について。
スコープ情報の不足分は後日追記予定。(頭の中整理して書くの疲れるぅ〜。。:joy:)

class A # Aクラス(通常のクラス)
  @@class_variable = "クラス変数"

  # @class_instance_variable = "クラスインスタンス変数"
  # => ここで宣言した場合、それは「クラスインスタンス変数」と見なされる。
  # 「インスタンス変数」と同じプレフィックス。注:ダブルで記載してしまった場合、こっちが優先されてしまう

  def initialize # インスタンスメソッド
    @instance_variable = "インスタンス変数"
    puts "#{@instance_variable} at initialize"
  end

  def hoge # インスタンスメソッド
    puts "#{@instance_variable} at xxx"
  end

  def self.fuga # クラスメソッド(定義の方法:特異メソッド方式)
    puts "#{@instance_variable} at yyy"
  end

  class << self # 特異クラス
    def fuga # クラスメソッド(定義の方法:特異メソッド方式)
      puts "#{@instance_variable} at yyy"
    end
  end
end

# インスタンスメソッドinitializeからはアクセスできる
instance = A.new # => "インスタンス変数 at initialize" # インスタンス生成

instance.hoge # インスタンスメソッド呼び出し
# インスタンス変数は他のインスタンスメソッドでも使える
instance.hoge # => 'インスタンス変数 at xxx'

A.new.hoge # インスタンスメソッド呼び出し(クラス名.new.インスタンスメソッド名)
A.new.hoge # => 'インスタンス変数 at xxx'

A.fuga # クラスメソッド呼び出し
# クラスメソッドからはインスタンス変数(@〜)は使えない
A.fuga # => nil

参考記事:relaxed:

@mogulla3さん
- https://qiita.com/mogulla3/items/cd4d6e188c34c6819709
@tbpgrさん
- https://qiita.com/tbpgr/items/56eb65c0ea5882abbb07

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

【自己用メモ】(Ruby)クラス、クラスインスタンス、インスタンス

用語

各種変数・メソッド用語について。
スコープ情報の不足分は後日追記予定。(頭の中整理して書くの疲れるぅ〜。。:joy:)
おかしなところ、お気づきな点等ございましたら、
お手数ですがご指導ご指摘、頂けますと幸いです:hand_splayed_tone2:

class A # Aクラス(通常のクラス)
  @@class_variable = "クラス変数"

  # @class_instance_variable = "クラスインスタンス変数"
  # => ここで宣言した場合、「インスタンス変数」ではなく、「クラスインスタンス変数」と見なされる。
  # 「インスタンス変数」と同じプレフィックス(@〜)。

  def initialize # インスタンスメソッド
    @instance_variable = "インスタンス変数"
    puts "#{@instance_variable} at initialize"
  end

  def hoge # インスタンスメソッド
    puts "#{@instance_variable} at xxx"
  end

  def self.fuga # クラスメソッド(定義の方法:特異メソッド方式)
    puts "#{@instance_variable} at yyy"
  end

  class << self # 特異クラス
    def fuga # クラスメソッド(定義の方法:特異クラス方式)
      puts "#{@instance_variable} at yyy"
    end
  end
end

# インスタンスメソッドinitializeからはアクセスできる
instance = A.new # => "インスタンス変数 at initialize" # インスタンス生成

instance.hoge # インスタンスメソッド呼び出し
# インスタンス変数は他のインスタンスメソッドでも使える
instance.hoge # => 'インスタンス変数 at xxx'

A.new.hoge # インスタンスメソッド呼び出し(クラス名.new.インスタンスメソッド名)
A.new.hoge # => 'インスタンス変数 at xxx'

A.fuga # クラスメソッド呼び出し
# クラスメソッドからはインスタンス変数(@〜)は使えない
A.fuga # => nil

参考記事

:relaxed:
@mogulla3さん
- https://qiita.com/mogulla3/items/cd4d6e188c34c6819709
@tbpgrさん
- https://qiita.com/tbpgr/items/56eb65c0ea5882abbb07

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

【Ruby】三項演算子でif文のコードをダイエットしてみた

内容

プログラミング言語の演算子のひとつに、三項演算子(条件演算子)という呼ばれるものがあります。
これを使うと、複数行に渡るif文を1行で記述することができるので、モッサリしたコードをダイエットすることができます。

この記事ではRubyのコードで書かれた
「入力された整数が奇数であれば"odd"を、偶数であれば"even"と出力するプログラム」
を題材としながら、if文で書かれたコードをダイエットしていく様を見て頂きます。

まずは、これから書いていくコードのイメージをつくっていきましょう!

if 数字が偶数
  evenと表示
else
  oddと表示
end

以上のような5行に渡るif文を

数字が偶数 ? evenと表示 : oddと表示

と1行で書くことができることが三項演算子の特徴です。

前提環境

MacOS Catalina 10.15.7
Ruby 2.6.6

if ~ else 文

if.rb
n = gets.to_i

if n % 2 == 0
  check_number = "even"
else
  check_number = "odd"
end

puts check_number

このrubyファイルをターミナルで実行すると以下のようになります。
下記の例以外でも奇数であればodd、偶数であればevenが出力されるはずです。

$ ruby if.rb
5                 # 入力
odd         # 出力

$ ruby if.rb
8                 # 入力
even         # 出力

三項演算子

ternary.rb
n = gets.to_i

check_number =  n % 2 == 0 ? "even" : "odd"

puts check_number

三項演算子で書くと以上のようになります。if ~ else 文を利用した場合よりもだいぶスッキリしましたね!
行数で言うと7行→3行となっており半分以下になっております。

念のためターミナルで実行してみましょう。if ~ else 文の時と同様の結果が得られますね。

$ ruby ternary.rb
17                 # 入力
odd          # 出力

$ ruby ternary.rb
24                 # 入力
even          # 出力

三項演算子を学ぶだけであれば以上で終了なのですが、、、
せっかくなのでもう少しダイエットを続けてみましょう!!!!!

if ~ else 文(メソッド定義なし)

if_rev.rb
n = gets.to_i

if n % 2 == 0
  puts "even"
else
  puts "odd"
end

メソッド定義を使わずにif文の中にputsを書きました。
最初のコードより少しだけ短くなっています。

三項演算子(メソッド定義なし)

ternary_rev.rb
n = gets.to_i

puts  n % 2 == 0 ? "even" : "odd"

上記の書き方を三項演算子で書くとこのようなコードになります。
2行になりましたね!ここまで来ると欲が出てきて1行で書いてみたくなりませんか?

三項演算子(最終版)

ternary_final.rb
puts  gets.to_i % 2 == 0 ? "even" : "odd"

1行で、、、書けた!!!!!
もともと7行で書いていたコードが1行になりました。
まさかの85%OFFのダイエット、これにはR○Z○Pさんもビックリですね。

以上、三項演算子を使ったコードのダイエットでした!

追記

2020/01/08
@scivola 様から頂いたご指摘を紹介します(ありがとうございました!)

以下のようにカッコをつけると見やすくなる。

ternary_final.rb
puts (gets.to_i % 2 == 0) ? "even" : "odd"

整数が偶数かどうかを判定するのは Integer#even?が使えるので,

ternary_final.rb
puts gets.to_i.even? ? "even" : "odd"

と書くこともできるとのこと!

@scivola 様ありがとうございました!

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

Progate Ruby 学習コースⅡ 修了

学んだこと

自分用の備忘録メモなので手短に要点だけ…

  • 配列の際は [ ] を忘れない
  • 配列のインデックス番号は0から始まる。(0,1,2...)
  • ハッシュの使い方
  • ハッシュの書き方のひとつ「シンボル」
  • ハッシュを省略した書き方
    • ハッシュには3通りの書き方があるが「省略形」が一般的。
  • 「nil」 = なにもないの意
  • Rubyでの繰り返し処理は「each文」を使う
  • Each文内の変数名は、配列の変数名の単数形にすることが(慣習上)多い。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む