20210308のRubyに関する記事は24件です。

【Ruby】Base64変換ツール書いてみた

ここの問題を解いてみました

コード

module Base64
  TABLE = [*"A".."Z", *"a".."z", *"0".."9", "+", "/"]

  module_function

  def encode(str)
    binary_ary = str.bytes.map do |b|
      t = b.to_s(2)
      t.size == 8 ? t : t.rjust(8, "0")
    end

    binary_str = binary_ary.join

    binary_ary  = binary_str.scan(/\d{6}/)
    binary_ary << binary_str[-(binary_str.size % 6)..].ljust(6, "0") unless binary_str.size % 6 == 0

    binary_ary.map { |x| TABLE[x.to_i(2)] }.join << "=" * (binary_ary.size % 4)
  end

  def decode(base_str)
    binary_ary = base_str.delete("=").chars.map do |s|
      t = TABLE.index(s).to_s(2)
      t.size == 6 ? t : t.rjust(6, "0")
    end

    chr_code_ary = binary_ary.join.scan(/\d{8}/).map { |b| b.to_i(2) }
    chr_code_ary.pack("c*")
  end
end

乾燥した感想

解き終わったとき最高に気持ちよかったです
改善点などあればぜひご指摘お願いします

binary_ary << binary_str[-(binary_str.size % 6)..].ljust(6, "0") unless binary_str.size % 6 == 0

ここのunless修飾子以降を書き忘れるという凡ミスを犯し、それに気づくのにめちゃくちゃ時間がかかりました
作業時間の80%は最後の20%の作業にかかる時間であるっていう至言を完全に理解した

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

【Ruby on Rails】フォロー・フォロワー機能の実装

はじめに

Railsで作成中のアプリにフォロー・フォロワー機能を実装しました。
理解できていなかったことも多かったので、理解を深めるために投稿します。
解説が長すぎるので、全てリンクにしています。
参考にされる際は必要なところだけご覧いただいても構いません。

前提条件と作成物

前提条件
  • rails 5.2.4
  • devise導入済み
  • userテーブルは作成済み←Userに対しフォロー機能を設定します
作成物
  • フォロー機能(follow,unfollow)
  • フォロー・フォロワー一覧ページ

流れ

  1. Relationshipモデルとカラムを作成
  2. アソシエーションを記述
  3. Userモデルに
    「フォロー機能」「フォロー解除機能」「フォローしているかどうかメソッド」を記述
  4. Relationshipコントローラを作成・編集、ルーティング編集
  5. viewページを作成(フォローボタン)
  6. viewページを作成(フォロー・フォロワー一覧)

1. Relationshipモデルを作成

ターミナル
$ rails g model Relationship follower_id:integer followed_id:integer
# follower_id:フォローするユーザーのid, followed_id:フォローされるユーザーのid
$ rails db:migrate
# マイグレーション実行

まずはRelationshipモデルとカラムを作ります。
今回はRelationshipで作りましたが、他のモデルの場合は置き換えてください。

カラム データ型 備考
relationship_id integer 記述不要、自動作成される
follower_id integer フォローするユーザーのid
followed_id integer フォローされるユーザーのid

【解説】なぜRelationshipモデルのカラムはuser_idではないのか?(クリック)
Relationshipモデルは中間テーブルの役割を担っています。
中間テーブルとは、多 対 多のリレーションを構築するとき、
2つのテーブルを関連づけるために各テーブルの外部キー(FK)をもつテーブルのことです。
今回のフォロー・フォロワー機能は、
・1ユーザーはたくさんのユーザーにフォローできる(1:N)
・1ユーザーはたくさんのユーザーにフォローされる(1:N)
つまり多 対 多のリレーション状態を作る機能です。
通常であれば、参照するテーブルのid(この場合はuser_id)を中間テーブルのカラムに当てることが望ましいですが、
今回の場合は参照されるのは両方ともuserテーブルとなり、重複してしまいます。
そのため、最終的に参照するのはuser_idですが、
分かりやすいように別のカラム名で定義をしてあげる必要があります。
今回は、
follower_id : フォローするユーザーのid
followed_id : フォローされるユーザーのid
としてカラムを設定しました。

中間テーブル?についてはこちら↓
Active Record の関連付け

2. アソシエーションを記述

app/models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
  # class_name: "User"を定義することでUserテーブルのレコードを参照する
end

Relationshipモデルにアソシエーションを記述します。
belongs_to :follower の後にclass_name: "User"を忘れないようにします。


【解説】なぜbelongs_to :follower(followed)なのか?class_nameはなぜ必要?(クリック)
belongs_to :follower, class_name: "User"
を元に見ていきます。
belongs_to = 従属 の意味です。


belongs_to :followerでfollowerテーブルのfollower_idを探しにいきます。
ただ、今回はfollowerテーブルは作成していないのと、
参照してもらいたいのはfollower_idに格納されている値と同じuser_idです。
(user_idはフォロー・フォロワー関係にありますが、
relationshipテーブルの中で重複するため定義できません。
そのため、このような現象が起こります。)
このままではエラーになってしまうため、
class_name: "User"でUserテーブルを参照するように定義します。


これを行うことで、例えば
「relationship_idが(1)の時follower_idに格納されている値と
同じuser_idをUserテーブルから探して」

というように関連づけることができます。(あくまで私の捉え方です)
followed_idに関しても同じなので、同様に定義します。

app/models/user.rb(フォロー機能部分のみ抜粋)
class User < ApplicationRecord
  has_many :reverse_of_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy
  has_many :followers, through: :reverse_of_relationships, source: :follower
  # 被フォロー関係を通じて参照→followed_idをフォローしている人

  has_many :relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
  # 【class_name: "Relationship"】は省略可能
  has_many :followings, through: :relationships, source: :followed
  # 与フォロー関係を通じて参照→follower_idをフォローしている人
end

UserモデルにもRelationshipとのアソシエーションを記述します。
くどいようですが、user_idを中間テーブルで直接定義していないため、
Relationshipテーブルを参照した時にuser_idが参照できるよう、定義します。
また、今回はフォロー・フォロワー一覧ページも作成するため、2、4行目も記述します。
(フォロー・フォロワー数をカウントする時も使えます。)


【解説】has_many 1行目と3行目の意味は?(クリック)
has_many :reverse_of_relationships, class_name: "Relationship"
先程のRelationshipモデルと同じ意味です。
見分けやすいようにreverse_of_relationshipsと定義しました。
このままだと、reverse_of_relationship_idを探してしまいます。
それを防ぐために
foreign_key: "followed_id"でどのカラムを参照して欲しいのかを定義します。
(foreign_keyは外部キーの意味です)



has_many :relationships, class_name: "Relationship"
の場合、class_nameは不要ですが、見た目統一のためにコードを残しています。


【解説】has_many 2行目と4行目の意味は?(クリック)
2行目のhas_many :followers, through: :reverse_of_relationships, source: :follower
を基準に見ていきます。


1行目で定義したのは、「どのカラムを参照するか」でした。
2行目と4行目では、参照したカラムを元に、関連するuser_idも参照できるように記述します。
1行目で定義したreverse_of_relationshipsモデルをthroughしてfollower(_id)というカラムを参照(source)してね、と記述しています。


これを記述することで、@user.followersという記述がコントローラーで使えるようになります。
回りくどい説明になってしまいますが、2行目の場合は、
followed_idにあたるユーザーをフォローしているユーザー(つまりfollower_idにあたるユーザー)を参照することができます。
これはフォロー・フォロワー一覧ページを作成する時に使います。

3. Userモデルにメソッドを記述

app/models/user.rb(フォロー機能部分のみ抜粋)
class User < ApplicationRecord
  def follow(user_id)
    relationships.create(followed_id: user_id)
  end
  def unfollow(user_id)
    relationships.find_by(followed_id: user_id).destroy
  end
  def following?(user)
    followings.include?(user)
  end
end

4. Relationshipコントローラを作成・編集

まずはrelationshipsコントローラを作成します。

ターミナル
$ rails g controller relationships

作成したコントローラにフォロー機能を作成・保存・削除するアクションと、
フォロー・フォロワー一覧を表示するアクションを記述していきます。

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
# ——————フォロー機能を作成・保存・削除する————————————
  def create
    current_user.follow(params[:user_id])
    redirect_to request.referer
  end

  def destroy
    current_user.unfollow(params[:user_id])
    redirect_to request.referer  
  end
#————————フォロー・フォロワー一覧を表示する-————————————
  def followings
    user = User.find(params[:user_id])
    @users = user.followings
  end

  def followers
    user = User.find(params[:user_id])
    @users = user.followers
end


【解説】フォローを作成・保存・削除する(クリック)
current_user.follow(params[:user_id])
current_user.unfollow(params[:user_id])では、先程userモデルで定義したfollow、unfollowメソッドを使っています。
relationshipsとusersはネストの関係のため、userモデルからメソッドの呼び出しも可能です。


モデルに記述する理由はこちらが参考になりました↓
Railsのモデルに書いたメソッドってどうやってコントローラで使うの?


【解説】フォロー・フォロワー一覧を表示する(クリック)
@users = user.followings
@users = user.followersでは、
先程userモデルで定義したアソシエーションを利用します。
これによりuser = User.find(params[:user_id])で取得したユーザーのidが、
フォローしている もしくは フォローされている ユーザーのid一覧を
取得することができます。

ルーティングも記述します。

config/routes.rb(関連部分のみ)
resources :users, only: [:index, :show, :edit, :update] do
# ——————————————— ここから ———————————————
  resource :relationships, only: [:create, :destroy]
  get 'followings' => 'relationships#followings', as: 'followings'
  get 'followers' => 'relationships#followers', as: 'followers'
# ——————————— ここまでネストさせる ———————————
end

resources :usersの最後にdoを記述するのを忘れないようにします。
relationships は中間テーブルなので、usersモデルにネストさせます。
followingsとfollowersは一覧ページ用に定義したアクションです。

5. viewページを作成(フォローボタン)

view/users/_info.html.erb(フォローボタン部分のみ記述)
<% if current_user.following?(user) %>
  <%= link_to "Unfollow", user_relationships_path(user.id), method: :delete %>
<% else %>
  <%= link_to "Follow", user_relationships_path(user.id), method: :post %>
<% end %>

今回はuserページの部分テンプレート内に埋め込みましたが、
view/relationships/_follow_button.html.erbなど、
ボタン部分だけ記述したviewを作成してもOKだと思います。(そちらの方が見やすいです)


【解説】<% if current_user.following?(user) %>(クリック)
if current_user.following?(user)では、
userモデルで定義したfollowing?メソッドを使っています。
これにより、「今ログインしているユーザーは今参照しているユーザーをフォロしているか?」が条件となります。
trueの場合は、フォローしている状態なので、フォロー解除(unfollow)できるような表示を、
falseの場合は、フォローしていない状態なので、フォローする(follow)できるように表示します。

6. viewページを作成(フォロー・フォロワー一覧)

view/relationships/followers.html.erbもしくはview/relationships/followings.html.erb
<h2>Follower Users</h2>または<h2>Follow Users</h2>
<% if @users.exists? %>
  <thead>
    <tr>
      <th>name</th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <% users.each do |user| %>
      <tr>
        <td><%= user.name %></td>
        <td>フォロー数: <%= user.followings.count %></td>
        <td>フォロワー数: <%= user.followers.count %></td>
      </tr>
    <% end %>
  </tbody>
</table>
<% else %>
  <p>ユーザーはいません</p>
<% end %>

フォロー・フォロワー一覧ともにほとんど同じ記述です。
タイトルを<h2>Follower Users</h2>なのか、
<h2>Follow Users</h2>なのか選択します。
<% if @users.exists? %>trueの場合は、ユーザー一覧を表示させます。
すでにユーザー一覧(index)を作成していれば、部分テンプレート化して埋め込みでOKです。
例:<%= render '/users/index', users: @users %>などです。
作成していない場合は、上記のような形で表記できます。
tableタグだけで整えていますが、bootstrapなどを使えばより綺麗に整えられると思います。

これでフォロー・フォロワー機能の実装は完了です!

おわりに

今回は、フォロー・フォロワー機能の実装を、手順と内容についてまとめました。
1度の実装だけでは理解が難しく、何度もパターンを試して、
アウトプットすることでまた少し理解が深まったような気がします。
フォロー・フォロワー機能の実装方法はたくさんあると思うので、
よりスマートで効率の良いコードを書けるように精進していきたいです^^

内容が盛りだくさんのため、抜け漏れがあったら申し訳ありません。
また、理解が乏しい箇所・用語の使い方が間違っている点も多々あるかと思います。コメント欄でご指摘いただければ幸いです。

こちらも参考になりました↓ありがとうございました。
https://qiita.com/mitsumitsu1128/items/e41e2ff37f143db81897

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

Railsポートフォリオで詰まった箇所まとめ(その都度追加)

Railsポートフォリオ作成中、時間を取られた箇所まとめ。精神的に来たもの一覧でもある。ほぼ自分への戒め用。もう少し早く書き始めればよかった・・・

【ページ内リンク】

0.環境
Q1.フォルダ全消しした後、再度同じものを作り直したときに途中でエラーの嵐になる
Q2.テーブルにDateTimeが追加できない!

0.環境

・AWS
・heroku/7.48.0 linux-x64 node-v12.16.2
・Rails 5.2.4.5
・ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
・MySQL 5.7.31
・PostgreSQL 9.2.24

Q1.フォルダ全消しした後、再度同じものを作り直したときに途中でエラーの嵐になる

A.前に作ったデータベース消してなかった。そりゃ同名のDB作ろうとしたらエラー出ますよね。

【解決策】

mysql> drop database 消したいDB名;

・消したいDB名の確認方法はmysql> show database;

ページ内リンクへ戻る

Q2.テーブルにDateTimeが追加できない!

A.諦めてdateクラスとtimeクラスに分けた・・・(後に、DateTimeって大文字DTを使ってたからだと判明。これは酷い。)

【経緯】
・日付と時刻を入れたかったので、それら二つとも代入できるDateTimeクラスって便利じゃん!ということで$ rails g model テーブル名 カラム名:DateTimeをやってみたが$ rails db:migrateでエラー。
・エラー文読んでみたらDateTimeがダメみたい
TimeでもDateでも出来なかった
・色々考えた結果、$ rails g model テーブル名 カラム名:date ~としたら出来た
・普通に$ rails g model テーブル名 カラム名:datetimeとしたら出来るのでは・・・?
・出来た。

【解決策】

$ rails g model テーブル名 カラム名:datetime

ページ内リンクへ戻る

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

Two Sum 〜leetcode〜

〜問題文〜

Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

You can return the answer in any order.


_________________________________
Example 1:

Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Output: Because nums[0] + nums[1] == 9, we return [0, 1].
Example 2:

Input: nums = [3,2,4], target = 6
Output: [1,2]
Example 3:

Input: nums = [3,3], target = 6
Output: [0,1]


Constraints:

2 <= nums.length <= 103
-109 <= nums[i] <= 109
-109 <= target <= 109
Only one valid answer exists.

引用元: https://leetcode.com/problems/two-sum/
  

簡単に要約すると、

「与えられた配列(nums)内の数字同士で、足し合わせると、targetになる配列要素の添字(インデックス)を、2つ、配列として出力するようなメソッドを作りなさい。」

だそうです。  

〜回答例〜

def two_sum(nums, target)
    search = Hash.new
    nums.each_with_index do |item,index|
        i = search[target-item]
        return [i+1, index+1] if i != nil
        search[item] = index
    end
end

回答例引用元(一部改変):
https://leetcode.com/problems/two-sum/discuss/55/Accepted-ruby-solution

以下、上記回答の分解分析

1. メソッドの定義

def two_sum(nums, target)

        〜省略〜

end

「two_sum」というメソッドで第一仮引数=「nums」と第二仮引数= 「target」と定義する。

2. 変数searchに代入

         〜省略〜

    search = Hash.new

         〜省略〜

変数「search」に対して、「Hash.new」を代入する。

Hash.newについては以下を参考にする。

参考:https://docs.ruby-lang.org/ja/latest/method/Hash/s/new.html


    nums.each_with_index do |item,index|

          〜省略〜

    end

「each_with_index」メソッド。
要素とその添字(インデックス)をブロックに渡して繰り返すメソッド。

参考:https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/each_with_index.html

今回場合、ブロック変数の「index」部分に、添字(インデックス)が入ってくる。
また、添字は0から開始する。

3. 以下、繰り返し部分


 i = search[target-item]

変数「 i 」に「search(=Hash.new)」を代入する。
「search」の添字の指定は、「target(=本メソッドの仮引数)」-「item(=numsの各要素)」で行う。


  return [i+1, index+1] if i != nil


「return ~ if」の書き方は以下を参考にした。
参考:https://qiita.com/okhrt/items/3e98ba496bcf8c50ac64

つまり「return 処理 if 条件」。

「i (=search(=Hash.new))がnil(空)でなければ、 「i+1」 と 「index(=添字)+1」を配列で返しなさい。」という意味。

全体の最終的ゴールの出力はこの行。

 search[item] = index

継続する場合はこちらに続く。
「search(=Hash.new)」の添え時として、「item(=numsの各要素)」を指定してあげた数値は、indexに代入される。

処理後再びループへ。

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

【Ruby】to_iメソッドで値を整数に変換する。

個人メモです。

to_iを使うと整数に変換できる。

to_i
test = { x: "123", y: "456", z: "789" }
puts test[:x].class  #String
puts test[:x].to_i.class #Integer

classは型を調べるメソッド。


to_i
test = { x: "123", y: "456", z: "789" }
puts test[:x].is_a?(Integer)  #false
puts test[:x].to_i.is_a?(Integer)  #true

.is_a?(型)は指定した型かどうかを調べるメソッド。


次の処理は何を意味しているか?

    per_page = params[:per_page].to_i

シンボルparamsの中のper_pageの値を取得し整数に変換したものを、per_pageに格納している。

つまり、per_pageにはただの整数が入っている。

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

【Ruby】before_actionとは何か?引数を渡す方法とオプションの使い方実例。

【Ruby】before_actionとは何か?引数を渡す方法とオプションの使い方実例。

before_actionとは何か?

各アクションが動く前に実行する処理を記述できる。
コントローラの冒頭に記述する。

記述方法

  1. 引数なしの場合
  2. 引数ありの場合
  3. オプション

1. 引数なしの場合

before_action :<メソッド名>

メソッドは同じファイルの下部に記述する。(※引数を渡すとエラーになる)



▼実例

before_actionの例
class UsersController < ApplicationController

  before_action :require_permission

  #(省略)アクションを記述

  #before_actionの処理
  def require_permission
    unless current_user.partner? || current_user.admin?
      redirect_to admin_root_path, alert: 'ここから先は管理者限定です!'
    end
end

アクションを実行する前に、require_permissionを実行する。
もし、current_userのpartnerかadminの値が存在しなければ、admin_root_pathにリダイレクトする。その際にalartを表示する。

unless文

条件式がfalseなら処理を実行する。
if !条件式と同じ

unless 条件式 
  処理
end

||

または(or)。

オブジェクトcurrent_userのpartnerまたはadminの値が存在すればtrue, 存在しなければfalseを返す。

redirect_to

redirect_to <リダイレクト先>[, <オプション名>: <値>]

alert:で、アラートを表示する

(参考)Ruby公式 redirect_to


2. 引数ありの場合

処理に引数を渡す場合は、矢印と波かっこ→ { }を使う。

before_action ->{ 処理 }

  • 引数を渡さない場合でも使える。

▼実例

before_actionの例
class UsersController < ApplicationController

  before_action ->{
    require_permission("password")
  }

  #(省略)アクションを記述

  #before_actionの処理
  def require_permission("password")
    unless password === "xxxxx"
      redirect_to admin_root_path, alert: 'ここから先は管理者限定です!'
    end
end


処理を複数記述する

処理を複数記述する場合も、->{ }を使う。

  before_action ->{ 処理1, 処理2 }


3. オプション

オプションをつけることで条件を絞ることができる。

オプション 内容
:only 実行するアクション
:except 実行しないアクション
:if 実行する条件
:unless 実行しない条件

例えば、createとshowアクションのみに適用したい場合は、only: [:create, :show]をつける。

before_actionの例
class UsersController < ApplicationController

  before_action :require_permission, only: [:create, :show]

  #(一部省略)アクションを記述
    create: {
      create_params: :users_params,
      json_renderer: :show_renderer,
    },
    show: {
      json_renderer: :show_renderer,
    },

  #before_actionの処理
  def require_permission
    unless current_user.partner? || current_user.admin?
      redirect_to admin_root_path, alert: 'ここから先は管理者限定です!'
    end
end



->{ }を使った場合も同じ

before_actionの例
class UsersController < ApplicationController

  before_action ->{require_permission}, only: [:create, :show]

  #(一部省略)アクションを記述
    create: {
      create_params: :users_params,
      json_renderer: :show_renderer,
    },
    show: {
      json_renderer: :show_renderer,
    },

  #before_actionの処理
  def require_permission
    unless current_user.partner? || current_user.admin?
      redirect_to admin_root_path, alert: 'ここから先は管理者限定です!'
    end
end



Rails公式 before_action

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

RSpecにて引数が必要なsubjectの使い方

はじめに

RSpecで、同じメソッドに対してのテストを複数回書くシーンでは、subjectを使うことでDRYなコードを書くことができます。
ここでは引数が必要なメソッドのテストをsubjectを用いて書きます。

実装

ユーザー名(user_name)、自己紹介(pr)カラムがあるユーザーを、フリーワードで検索するuser_search(free_word)メソッドをテストするとします。普通に書くと以下のように繰り返し同じ処理が出てきます。

spec/models/user_spec.rb
describe "#user_search" do
  context "when 'user_name1' is given" do
    it "return 1 result" do
      expect(Job.user_search("user_name1").count).to eq 1
    end
  end

  context "when 'pr1' is given" do
    it "return 1 result" do
      expect(Job.user_search("pr1").count).to eq 1
    end
  end
end

これは以下のようにsubjectでまとめて処理を書くことができます。

spec/models/user_spec.rb
describe "#user_search" do
  subject { User.user_search(field).count }

  context "when user_name1 is given" do
    let(:field) { "user_name1" }
    is_expected.to eq 1
  end

  context "when pr1 is given" do
    let(:field) { "pr1" }
    is_expected.to eq 1
  end
end

引数はletで定義することができます。

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

【Ruby】メソッド名や関数名の末尾の!(ビックリマーク)と?(はてなマーク)の意味とは?。実例で解説。

【Ruby】メソッド名の末尾の!(ビックリマーク)と?(はてなマーク)の意味について。

目次

  1. メソッド名末尾の!(ビックリマーク)の意味
  2. メソッド名末尾の?(クエスチョンマーク)の意味

1. メソッド名末尾の!(ビックリマーク)の意味

メソッド名の末尾についている!は処理を破壊的にする

条件式の中で使われる!は否定を表すので、完全に異なる。

▼実例
例えば、非破壊のメソッドdowncase(小文字にする)を使った場合、通常だと、元のオブジェクトは変化しない。

通常
str1 = "HELLO WORLD"
str1.downcase
puts str1 

# HELLO WORLD



!をつけると元のオブジェクトが変更(破壊)される。

!あり
str2 = "HELLO WORLD"
str2.downcase!
puts str2

 # hello world


定義した関数の中で使う場合

関数の中で破壊的メソッドを使う場合は、処理の中身がわかりやすいように、関数名の末尾に!をつける。

def letterCange!(str)
  str.downcase!
  puts str
end

#関数の実行
letterCange!("HELLO WORLD") 

#結果
hello world


2. メソッド名末尾の?(クエスチョンマーク)の意味

真偽値を返すメソッドに使われる。?もセットで一つのメソッド。(?なしだとエラーになる)

通常
str1 = "A STRING"
puts str1.include?("RING")

# true



?がないとエラーになる

?なし(エラー)
str2 = "A STRING"
puts str2.include("RING")

# NoMethodError (undefined method `include' for "A STRING":String)
# Did you mean?  include?

?のつく主なメソッド一覧

メソッド 意味
include? 含むかどうか "string".include?("ring") => true
empty? 空かどうか(Array, Hash, Set) [].empty? => true
nil? nilかどうか 0.nil? => false


参考リンク

Ruby公式 使われる記号の意味

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

tiktokでogp相当の情報をAPIで取得する(ruby)

tiktokのスクレイピングをしてogp情報をとってくる処理を書いていたのだがいつのまにか下記のようにstatus code1000で弾かれるようになってしまっていた。

[25] pry(main)> html = open(movie_url) do |f| f.read; end
=> "{\"statusCode\":10000,\"verifyConfig\":{\"code\":10000,\"type\":\"verify\",\"subtype\":\"slide\",\"fp\":\"verify_5f101e29a0ad54278347a24437cfcb93\",\"region\":\"sg\",\"detail\":\"2SdJGObfW85-lX4dEGohkmh8YbPmZYiXwIh1pqrEUuohsJqr4t3XjgXHtkBHWheNJbj4MB*CVwF1ayKFmhUFcJY2yAgBxyatF0XXzv0*xk*DmEz7Og6oE0nS1mFuZXdkJE*Z05uOnCgTkh4d4yy7xfv6EiiQJGBxdHDxp21vXCZjphcNMaY6YoCrq-5wRulWVle0lP3RJyqJQUFbtIP8pH0LICH8SRGn9a6mItvTEiEwiHCfJHBxhTUf1kFCYkGm1gBpVE1s45qbaNPeVELCCU0732nlU1VLR7Jm16yTIne64NABwJSaXFAl19NrjsfHrV-Cxq*d4kuVUBERzDIIncNGgQ3m6MLdwGLw*H4xfl94O1LId2V5zlOltVL1gYtral9VUYNMskUBWclUpWt2zrUfpbVzuUg.\"}}"

どうしようかなと思ったが、調べてみたらTikTokのAPIでogp相当の情報をとってこれるようだったので、apiを通して取得してくるように修正した。ドキュメントもあるので別に何も大変なことはない。

https://www.tiktok.com/oembed?url=https://www.tiktok.com/@scout2015/video/6718335390845095173

このような形で情報を取得したいtiktokのURLをurlパラメータに渡してhttps://www.tiktok.com/oembed を叩くだけでいい。そうすると下記のようなレスポンスが返ってくる。

{
  "version": "1.0",
  "type": "video",
  "title": "Scramble up ur name & I’ll try to guess it?❤️ #foryoupage #petsoftiktok #aesthetic",
  "author_url": "https://www.tiktok.com/@scout2015",
  "author_name": "Scout & Suki",
  "width": "100%",
  "height": "100%",
  "html": "<blockquote class=\"tiktok-embed\" cite=\"https://www.tiktok.com/@scout2015/video/6718335390845095173\" data-video-id=\"6718335390845095173\" style=\"max-width: 605px;min-width: 325px;\" > <section> <a target=\"_blank\" title=\"@scout2015\" href=\"https://www.tiktok.com/@scout2015\">@scout2015</a> <p>Scramble up ur name & I’ll try to guess it?❤️ <a title=\"foryoupage\" target=\"_blank\" href=\"https://www.tiktok.com/tag/foryoupage\">#foryoupage</a> <a title=\"petsoftiktok\" target=\"_blank\" href=\"https://www.tiktok.com/tag/petsoftiktok\">#petsoftiktok</a> <a title=\"aesthetic\" target=\"_blank\" href=\"https://www.tiktok.com/tag/aesthetic\">#aesthetic</a></p> <a target=\"_blank\" title=\"♬ original sound - ???????\" href=\"https://www.tiktok.com/music/original-sound-6689804660171082501\">♬ original sound - ???????</a> </section> </blockquote> <script async src=\"https://www.tiktok.com/embed.js\"></script>",
  "thumbnail_width": 720,
  "thumbnail_height": 1280,
  "thumbnail_url": "https://p16.muscdn.com/obj/tos-maliva-p-0068/06kv6rfcesljdjr45ukb0000d844090v0200010605",
  "provider_url": "https://www.tiktok.com",
  "provider_name": "TikTok"
}

コード(ruby)

uri = URI.parse("https://www.tiktok.com/oembed?url=#{params[:tiktok_url]}")
response = Net::HTTP.get_response(uri)
res_json = JSON.parse(response.body)
html_doc = res_json["html"]
title = res_json["title"]
ogp_image = res_json["thumbnail_url"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】クラスの中のアットマークの意味。@と@@の違いは?(インスタンス変数とクラス変数の実例)

【Ruby】クラスの中のアットマークの意味。@と@@の違いについて。

@と@@の違い

どちらも変数を表す。インスタンスで使う変数か、クラスで使う(すべてのインスタンスで共通)の変数かの違い。

項目 意味 使い方
@ インスタンス変数 @<変数名>
@@ クラス変数 @@<変数名>

インスタンス変数とクラス変数の使い方

インスタンス変数

インスタンス変数は、生成したインスタンスごとに値がセットされるため、それぞれで固有となる。

class Aaa
  #インスタンス変数を定義
  def setName(lastname, firstname)
    @name = lastname + " " + firstname
  end

  #メソッドを定義
  def hello
    p "#{@name}さん"
  end
end

#インスタンスの生成
p1 = Aaa.new
p2 = Aaa.new

#インスタンス変数に名前をセット
p1.setName("山田", "克巳")
p2.setName("田中", "次郎")

#インスタンスメソッドの呼び出し
p1.hello  # => "山田 克巳さん"
p2.hello  # => "田中 次郎さん"

クラス変数

クラス変数は、すべてのインスタンスで共通となる。上書きされるため、後からセットした値が入る。

class Aaa
  #クラス変数を定義
  def setName(lastname, firstname)
    @@name = lastname + " " + firstname
  end

  #メソッドを定義
  def hello
    p "#{@@name}さん"
  end
end

#インスタンスの生成
p1 = Aaa.new
p2 = Aaa.new

#インスタンス変数に名前をセット
p1.setName("山田", "克巳")
p2.setName("田中", "次郎")

#インスタンスメソッドの呼び出し
p1.hello  # => "田中 次郎さん"
p2.hello  # => "田中 次郎さん"

各インスタンス毎で名前をセットしたが、後からセットした名前で上書きされるため、出力はどちらも後からセットした値になる。

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

Ruby2.3.4+Rails5.0.4からRuby3.0.0+Rails6.1.3にバージョンアップした話

はじめに

Heroku-16 Stackで動いているアプリケーションがあり、こちらが2021/6にEOLを迎える。
それに伴いHeroku Stackを上げるとRubyのバージョンも上げないといけなかった。
せっかくだったらこの機会に最新にしようと思い、バージョンを上げたのでその時の注意事項をメモ書き程度に残しておく。

Gemfile

まず元々のGemfile

Gemfile
# 一部抜粋
gem 'rails', github: 'rails/rails', branch: "5-0-stable"
gem 'mysql2', '>= 0.3.18', '< 0.5'
gem 'puma', '~> 3.0'
gem 'rspec-rails', '~> 3.5'
gem 'factory_girl_rails'

Rubyのバージョン指定はしておらず、Herokuのデフォルトのバージョンになっていた。
今回はRubyとRailsのバージョンを上げたいので以下を設定してみた。

Gemfile
ruby "3.0.0"
gem 'rails', '~> 6.1', '>= 6.1.3'

これで環境を作り直そうとした結果以下のエラー

Bundler could not find compatible versions for gem "bundler":
  In Gemfile:
    bundler-audit was resolved to 0.6.0, which depends on
      bundler (~> 1.2)
    license_finder was resolved to 3.0.2, which depends on
      bundler
    rails (~> 6.1, >= 6.1.3) was resolved to 6.1.3, which depends on
      bundler (>= 1.15.0)
  Current Bundler version:
    bundler (2.2.3)
This Gemfile requires a different version of Bundler.
Perhaps you need to update Bundler by running `gem install bundler`?
Could not find gem 'bundler (~> 1.2)', which is required by gem 'bundler-audit',
in any of the sources.

bundler関連の問題でうまく環境が作れないようだった。
どうやらbundlerが1.15.3で作ろうとしているのが問題のようだった。
Gemfile.lockを消した状態でbundlerのバージョンを上げてbundle installした。
bundlerは2.2.11になった。

Gemfile.lock
# 一部抜粋
+ RUBY VERSION
+   ruby 3.0.0p0
BUNDLED WITH
-   1.15.3
+   2.2.11

rexml

gem installすると以下のエラー

LoadError - cannot load such file -- rexml/document

こちらの記事を参考にしました。

Gemfile
gem 'rexml'

factory_girl

DEPRECATION WARNING: The factory_girl gem is deprecated. Please upgrade to factory_bot. See https://github.com/thoughtbot/factory_bot/blob/v4.9.0/UPGRADE_FROM_FACTORY_GIRL.md for further instructions. (called from <top (required)> at /usr/src/app/config/application.rb:17)
porter-web-dev | /usr/local/bundle/gems/activesupport-6.1.3/lib/active_support/dependencies.rb:332:in `require': cannot load such file -- rexml/document (LoadError)

factory_girlは非推奨なのでfactory_botにバージョンアップする。

Gemfile
-  gem 'factory_girl_rails'
+  gem 'factory_bot'

rspecファイル内にてFactoryGirlをFactoryBotに置換。
更新が用意してくれている置換コマンドで割といい感じに置換できた。

mysql2

手元の環境はDockerで作成しているがmysql-clientがないと言われたのでdefault-mysql-clientに変更

Package mysql-client is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
Dockerfile
- RUN apt-get install -y mysql-client
+ RUN apt-get install -y default-mysql-client

環境を立ち上げてみたがDBアクセスで以下エラー

Puma caught this error: Error loading the 'mysql2' Active Record adapter. Missing a gem it depends on? can't activate mysql2 (~> 0.5), already activated mysql2-0.4.10. Make sure all dependencies are added to Gemfile. (LoadError)

mysql2のバージョンも上げる。

Gemfile
- gem 'mysql2', '>= 0.3.18', '< 0.5'
+ gem 'mysql2', '~> 0.5.3'

puma

ローカル環境が立ち上がらない。
こちらの記事を参考にしました。

config/initializers/new_framework_defaults.rb
- ActiveSupport.halt_callback_chains_on_return_false = false
+ #ActiveSupport.halt_callback_chains_on_return_false = false

ローカル環境を立ち上げると証明書関連のエラー
こちらの記事を参考にpumaのバージョンも上げる。

Gemfile
- gem 'puma', '~> 3.0'
+ gem 'puma', '~> 5.2', '>= 5.2.1'

Rspec

ローカル環境も立ち上がりある程度動くようになったのでテストを通してみたところ全部失敗した。

Failure/Error:
         raise WrongScopeError,
               "`#{name}` is not available from within an example (e.g. an " \
               "`it` block) or from constructs that run in the scope of an " \
               "example (e.g. `before`, `let`, etc). It is only available " \
               "on an example group (e.g. a `describe` or `context` block)."
         `name` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). It is only available on an example group (e.g. a `describe` or `context` block).

こちらの記事を参考にしました。

spec/factories/hoge.rb
- FactoryGirl.define do
+ FactoryBot.define do
  factory :hoge do
-    name 'テスト'
+    name {'テスト'}
  end
end
spec/spec_helper.rb
  config.before(:all) do
    FactoryBot.reload
  end

最終的に

以下のようなGemfileになった。

Gemfile
# 一部抜粋
ruby "3.0.0"
gem 'rails', '~> 6.1', '>= 6.1.3'
gem 'mysql2', '~> 0.5.3'
gem 'puma', '~> 5.2', '>= 5.2.1'
gem 'rexml'
gem 'rspec-rails', '~> 4.0', '>= 4.0.2'
gem 'factory_bot'

各バージョンの後方互換を調べる

ローカル環境が起動しひと通りアプリケーションが動くことを確認。
RSpecが全て通ることを確認。
この時点で8割方バージョンアップ完了だったが、念の為各バージョンで後方互換切られているところを中心に調べていくことにした。
Ruby2.3から3.0という記事はなかったので1バージョンずつ調べていった。

ruby2.4
特に問題なさそう。

ruby2.5
後方互換の話は特になし。

ruby2.6
範囲オブジェクトに影響あり。
git grep -i range
git grep "\.\."
あたりで範囲オブジェクトを使っている箇所を調べていった。

ruby2.7
こちらも範囲オブジェクト関連。

ruby3.0.0
1つずつ見てgrepしてみたが影響のありそうなものはそもそも使っていなかったので問題ないと判断した。

rails6.1.3
https://qiita.com/ryohashimoto/items/622c3bcfb3336cb9317e
https://railsguides.jp/upgrading_ruby_on_rails.html
cookiesの話が気になるがrails4との比較をしているのでrails5からのアップデートでは問題なしと判断した。

Heroku

以上の調査と修正によりバージョンアップ問題なしと判断し本番をHeroku-20 Stackに上げてデプロイ。
しかし本番でエラーが発生した。

ActiveRecord::ConnectionNotEstablished: SSL connection error: error:1425F102:SSL routines:ssl_choose_client_version:unsupported protocol

こちらの記事と同様の事象のようだった。
なんとか解消したかったが原因不明のため、記事と同様にHeroku-18 Stackにしたら問題なく動いた。

まとめ

今回所要時間15時間程度でバージョンアップすることができた。
ここまで早くできたのは以下の要因によるものと思われる。

  • 元のバージョンがそこまで低くなかった。
  • テストが書いてあった。
  • 該当アプリケーションがシンプルな作りで機能がそこまで多くなく、検証すべき項目が少なかった。

せっかくバージョンを上げたので新バージョンで使えるようになった機能とかを使っていきたい。
まだ全然調べられていないがRails6からは標準でバルクインサートできるようになったらしい。
activerecord-importとかgemを後入れしなくても良くなったのは嬉しい。
時間ができたら触ってみることにする。

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

【Ruby】includeとextendの違い。クラスメソッドとインスタンスメソッドの違いと実例。

【Ruby】includeとextendの違い。クラスメソッドとインスタンスメソッドの違いと実例。

目次

  1. includeとextendとは?
  2. includeとextendの違い
  3. クラスメソッドとインスタンスメソッドの違い
  4. クラスメソッドとインスタンスメソッドの使い分け
  5. includeとextendの実例


includeとextendとは?

どちらも、クラスの中でモジュールを呼び出すときに使う(mixin ミクシン)。

モジュールとmixinとは?

includeとextendの違い

読み込んだモジュールのメソッドを、インスタンスメソッドかクラスメソッドのどちらで読み込むかの違い。

メソッド 意味
include インスタンスメソッドとして読み込む
extend クラスメソッドとして読み込む


クラスメソッドとインスタンスメソッドの違い

その名の通り、クラスから呼び出せるメソッドか、インスタンスから呼び出せるメソッドかの違い。

メソッド 呼び出し
クラスメソッド <クラス名>.<メソッド名>
インスタンスメソッド <クラス名>.new.<メソッド名>

クラスメソッドは、クラスから直接メソッドを呼び出せる

インスタンスの場合は、クラスからnewを使って、インスタンスを生成して、メソッドを呼び出す


クラスメソッドとインスタンスメソッドの使い分け

メソッド 用途
クラスメソッド 全体で共通
インスタンスメソッド インスタンス毎に固有

インスタンスメソッド

メソッドの結果が生成したインスタンス毎に固有となるメソッドを定義していく。(例えば、苗字や名前を登録するなど。)

インスタンスメソッド
class <クラス名>
  def <メソッド名>
    処理
  end
end
呼び出し
<クラス名>.new.<メソッド名>


クラスメソッド

DBから指定したデータを抽出する場合など、どのインスタンスでも同じ答えが返るメソッドに使う

  • メソッド名の前にselfがつく
  • メソッドはクラス名の後で直接呼び出せる(インスタンスを生成する必要がない)
インスタンスメソッド
class <クラス名>
  def self.<メソッド名>
    処理
  end
end
呼び出し
<クラス名>.<メソッド名>


includeとextendの実例

include

インスタンスメソッドとして読み込む

include
module Aaa
  def hello(name)
    p "こんにちは、#{name}さん"
  end
end


#モジュールの読み込み(mixin)
class Xxx
  include Aaa
end 

#メソッドの実行
Xxx.new.hello("山田")

 => "こんにちは、山田さん"

▼クラスはNG

NG
Xxx.new.hello
NoMethodError (undefined method `hello' for Xxx:Class)


extend

クラスメソッドとして読み込む

extend
module Bbb
  def hello
    p "皆さん、こんにちは"
  end
end


#モジュールの読み込み(mixin)
class Yyy
  extend Bbb
end 

#メソッドの実行
Yyy.hello

 => "皆さん、こんにちは"

▼インスタンスはNG

NG
Yyy.new.hello
NoMethodError (undefined method `hello' for #<Yyy:0x00007f81b2083410>)



以上。

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

Rails newまでまとめてみた

アウトプット兼ねてまとめてみました。
*アドバイス訂正などありましたらご指摘お願いいたします。

ステップ1

mkdir xxx

でフォルダー作ってそこに移動

ステップ2

gem install rails 
↓
rbenv exec gem install bundler

rbenv execはrbenvでインストールしたrubyを使ってbindlerを入れるらしい。

ステップ3

rails new プロジェクト名 -d mysql

作成したプロジェクトに移動

bundle install --path vendor/bundle
二回目以降はbundle installのみでOK

ステップ4

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

【Ruby】モジュールとは?クラスの違いについて。モジュールの中のメソッドの呼び出し方

Rubyのモジュールとクラスの違いについて。モジュールの中のメソッドの呼び出し方実例と注意点。

目次

  1. モジュールとは?
  2. モジュールのメリット
    1. メリット1 複数継承可能
    2. メリット2 同じメソッド名を定義できる
  3. モジュールの呼び出し方法
    1. classの中で読み込む
    2. moduleの中でmodule_functonを定義する
    3. Objectとextendを使う
    4. NG事例
  4. mixinとは?

モジュールとは?

  • メソッドを記述しておく場所
  • moduleの中にdefでメソッドを書いていく
  • メソッドを複数定義できる
module <モジュール名>
  def <メソッド名1>
    処理1
  end

  def <メソッド名2>
    処理2
  end
  ,
  ,
  ,
end


モジュールのメリット

  1. 複数読み込み可能
  2. 同じメソッド名を定義できる

メリット1 複数継承可能

複数のクラスを継承したい(メソッドの書かれた複数のファイルを読み込みたい)場合の代替手段。

▼どういうこと?
Rubyのデメリットとして、クラスを一つしか継承できない(単一継承)がある。

このため、他のクラスを読み込んで、更に既に定義したメソッドを読み込みたい場合に、他のクラスを読み込むことができない。

この解決手段としてモジュールがある。モジュールを使えば、継承するクラスの他にもメソッドを読み込むことができるようになる


メリット2 同じメソッド名を定義できる

module自体の名前が異なれば、同じメソッドでも違う名前になる。(オブジェクトのKVと同じ。)

オブジェクトの場合
obj1 = {name: yamada, age: 24}
obj2 = {name: tanaka, age: 24}

#呼び出し
obj1.name  #yamada
obj2.name  #tanaka

nameという同じプロパティ名が存在するが、大元がobj1とobg2で異なるため、違う扱いになる。

▼モジュールの場合

module
module Aaa
  def hello
    p "こんにちは"
  end
end

module Bbb
  def hello
    p "ニーハオ"
  end
end

メソッド名はどちらもhelloだが、入れ物が異なるので問題ない。


モジュールの呼び出し方法

モジュールの呼び出し方法は2つ。

  1. classの中で読み込む
  2. moduleの中でmodule_functonを定義する
  3. Objectとextendを使う

1. classの中で読み込む

include <モジュール名>で読み込む
<クラス名>.new.<メソッド名>で実行する

メソッドの実行方法は、通常のクラスと同じくインスタンスを作成して、メソッドを呼び出す。

OK
#モジュールの定義
module Aaa
  def hello
    p "こんにちは"
  end
end

#クラスで読み込み
class Xxx
  include Aaa
end

#メソッドの実行
Xxx.new.hello

 => "こんにちは"

モジュールが複数ある場合

カンマで繋ぐ。

include <モジュール名1>, <モジュール名1>,,,

▼注意点
メソッド名が同じ場合は、上のモジュールのメソッドが読み込まれる。(下側は無視される)

OK(メソッド名が同じ場合)
#モジュールの定義
module Aaa
  def hello
    p "こんにちは"
  end
end

module Bbb
  def hello
    p "ニーハオ"
  end
end

#クラスで読み込み
class Xxx
  include Bbb, Aaa
end

#メソッドの実行
Xxx.new.hello

=> "こんにちは"

mixinとは?

クラスの中でmoduleを呼び出すことをmixin(ミクシン)と呼ぶ。

mixinというメソッドがあるわけではなく、classの中でincludeを使ってmoduleを呼び出すこと = mixinとなる。

<を継承と呼ぶのと同じく、ただの名称。


2. moduleの中でmodule_functonを定義する

module_function :<メソッド名>

▼注意点
- :の後にスペースを空けない(エラーになる)
- 記述するメソッドは一つずつ。(メソッドが複数ある場合は複数記述する。カンマでつなげない)

OK
#モジュールの定義
module Aaa
  def hello
    p "こんにちは"
  end

  module_function:hello
end

#メソッドの実行
Aaa.hello

 => "こんにちは"


3. Objectとextendを使う

Object.newで空のオブジェクトを生成し、.extend <モジュール名>で拡張すれば、メソッドが使用できる。

OK
#モジュールの定義
module Aaa
  def hello
    p "こんにちは"
  end
end

#オブジェクトの生成
obj = Object.new

#モジュールの拡張
obj.extend Aaa

#メソッドの呼び出し
obj.hello
 => "こんにちは"


NG事例

モジュールはクラスの中で定義した関数のように、<モジュール名>.new.<メソッド名>では呼び出せない。

NG
#モジュールの定義
module Aaa
  def hello
    p "こんにちは"
  end
end

#メソッドの実行
Aaa.new.hello

#エラー内容
NoMethodError (undefined method `hello' for Aaa:Module)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

小ネタ rspecでの比較を省略しないようにする

Failure/Error: expect(result).to be_a(Validation::Valid)
       expected #<Validation::Invalid value=#<Bundle id: nil, created_at: nil, updated_at: nil, action: nil, type: "B...on::Finder::NotFoundError: No feature_definition (site_linux) found for contract (hodor_contract)>]> to be a kind of Validation::Valid

この が見れずにデバッグに苦労すること、よくありますよね。

以下のような設定を追加することで、 に切り替わる文字長を調整できるため、…が表示されなくなります。

RSpec.configure do |config|
  config.expect_with :rspec do |c|
    c.max_formatted_output_length = 1000000
  end
end

参考: https://github.com/rspec/rspec-expectations/issues/991

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

GitHub Actions ReviewDog がエラー

ReviewDogをGithub actions経由で使っていたら急にエラーが発生

 Installing rubocop with extensions ... https://github.com/rubocop/rubocop
  ERROR:  While executing gem ... (Gem::FilePermissionError)
      You don't have write permissions for the /var/lib/gems/2.7.0 directory.

原因はubuntu-latetのデフォルトruby versionが 2.5.1から2.7.0に変わったこと。
https://github.com/actions/virtual-environments/issues/1816

reviewdoc.yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: ruby/setup-ruby@v1
      with:
         ruby-version: 2.6.5 #マイクロバージョンまで指定可能

こんな感じでyamlにrubyのバージョンを指定するようにすればOK
他に指定できるバージョンはこちらから参照可能
https://github.com/ruby/setup-ruby

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

AWSへデプロイ後にユーザー情報が更新できない現象への対処法

AWSへデプロイ後に、user.saveでユーザー情報が保存できない現象についての対処法

とあるプログラミング学習サイトを利用してRailsを学び、作成したアプリケーションをサーバーへデプロイしたところまでは良かったのですが、その後作成したユーザーのアイコンを変更できない現象に遭遇しました。手探りでの対処で苦戦しましたので、同じことを繰り返さないためにも投稿に残したいと思います。

現象が発生した元のコード

app/controllers/users_controller.rb
  def update
    @user = User.find_by(id: params[:id])
    @user.name = params[:name]
    @user.user_id = params[:user_id]
    @user.email = params[:email]
    if params[:image]
      @user.image_name = "#{@user.id}.jpg"
      image = params[:image]
      File.binwrite("public/user_images/#{@user.image_name}", image.read)
    end
    if params[:password] != nil
      @user.password = params[:password]
    end
    if @user.save
      flash[:notice] = "ユーザー情報を編集しました"
      redirect_to("/users/#{@user.user_id}")
    else
      render("users/#{@user.user_id}/edit")
    end
  end

このコードでローカルではうまくいっていたのですが、サーバーへデプロイ後はユーザー画像のみ保存されて、MySQLのデータは更新されませんでした。

試したこと

エラーが吐き出されていないか確認しましたが、log/production.logにはエラーらしきものはなく、mysql.logを確認しようとしましたがどこにあるのかわからず、できませんでした。

このためはじめはrails consoleで状況を確認しようとしましたが、RDSのデータベースには接続されていないのか、users = User.allでユーザー情報を取得しようとしても、users.count = 0の状態でした。

そこで直接MySQLへログインして、テーブルのデータをSQLで更新してみたところ、こちらはできましたので、MySQLを使用して更新する方法を取ることにしました。

現象が改善した後のコード

app/controllers/users_controller.rb
  def update
    id = @current_user.id
    updated = 0
    update_name_sql = "update users set name = '#{params[:name]}' where id =#{id};"
    updated = ActiveRecord::Base.connection.execute(update_name_sql)
    update_email_sql = "update users set email = '#{params[:email]}' where id =#{id};"
    updated = ActiveRecord::Base.connection.execute(update_email_sql)
    if params[:image]
      update_image_name_sql = "update users set image_name = '#{id}.jpg' where id =#{id};"
      updated = ActiveRecord::Base.connection.execute(update_image_name_sql)
      image = params[:image]
      File.binwrite("public/user_images/#{id}.jpg", image.read)
    end
    if params[:password]
hashed_password = BCrypt::Password.create(params[:password])
      update_password_sql = "update users set encrypted_password = '#{hashed_password}' where id =#{@current_user.id};"
      ActiveRecord::Base.connection.execute(update_password_sql)
    end
    if updated =! nil
      flash[:notice] = "ユーザー情報を編集しました"
      redirect_to("/users/#{@user.id}")
    else
      flash[:notice] = "データベースへの保存に失敗しました"
      render("users/edit")
    end
  end

SQLを直に作成して、ActiveRecord::Base.connection.execute(SQL)でMySQLのデータベースを直接更新する方法へ変えました。
(パスワードに関してもトラブルがありましたので、直接暗号化してから保存するコードに書き換えました)

まとめ

プログラミング学習サイトの良いところは、手軽に言語の学習に手をつけられるところですね。しかし現実には実用レベルに達するためにいくつもの壁を越えなければいけないものなのだと実感しています。ローカルでは動いていたけれど、サーバー環境ではうまくいかないことが普通にあるのだとわかりました。これに懲りずにポートフォリオ作りに励もうと思います!

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

画像を投稿するときに、undefined method `images' for nil:NilClass担ってしまう問題を解決する

今回、Active Storageを使って、画像を投稿する機能を作りました。
しかし、投稿自体はあるものの、投稿から投稿一覧に遷移するときに、タイトルのエラーになってしまうという問題にぶち当たりました。

結論から言うと、viewの引数の指定が間違っていたためです。なので、そこを修正してあげることで解決しました

以下、投稿一覧ページの、がぞうを表示する部分です

   <% if @post.images.attached? %>
         <div class = 'images'>
         <% @post.images.each do |image| %>
         <%= image_tag post.images %>
         </div>
         <% end %>
         <% end %>

こちらを間違えてしまってしました。

それを、こういった形に修正しました。

<% if post.images.attached? %>
    <div class = 'images'>
    <% post.images.each do |image| %>
    <%= image_tag image %>
    </div>
    <% end %>
    <% end %>

このように、インスタンス変数を指定していいたのですが、それらを取っ払い、image_tagの部分のimagesを、imageに変えました。

参考記事

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

画像を投稿するときに、undefined method `images' for nil:NilClassになってしまう問題を解決する

今回、Active Storageを使って、画像を投稿する機能を作りました。
しかし、投稿自体はあるものの、投稿から投稿一覧に遷移するときに、タイトルのエラーになってしまうという問題にぶち当たりました。

結論から言うと、viewの引数の指定が間違っていたためです。なので、そこを修正してあげることで解決しました

以下、投稿一覧ページの、がぞうを表示する部分です

   <% if @post.images.attached? %>
         <div class = 'images'>
         <% @post.images.each do |image| %>
         <%= image_tag post.images %>
         </div>
         <% end %>
         <% end %>

こちらを間違えてしまってしました。

それを、こういった形に修正しました。

<% if post.images.attached? %>
    <div class = 'images'>
    <% post.images.each do |image| %>
    <%= image_tag image %>
    </div>
    <% end %>
    <% end %>

このように、インスタンス変数を指定していいたのですが、それらを取っ払い、image_tagの部分のimagesを、imageに変えました。

参考記事

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

testing

tesig

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

カラムから外部キーを削除する

アプリを作成している中で外部キーを削除する必要が出てきたので今後のためにも記事を残します。

/schema.rb

 create_table "behavior_histories", force: :cascade do |t|
    t.bigint "care_recipitent_id"
    t.date "behavior_history_date", null: false
    t.text "action_record", null: false
    t.time "behavior_time", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.bigint "family_id"
    t.index ["family_id"], name: "index_behavior_histories_on_family_id"
    t.index ["care_recipitent_id"], name: "index_behavior_histories_on_care_recipitent_id"

この中の外部キーであるcare_recipitent_idを削除します。

migrationファイルの作成

rails g migration remove_foreign_key_to_behavior_histories

外部キーの削除

class RemoveForeignKeyToBehaviorHistories < ActiveRecord::Migration[6.0]
  def change
    remove_foreign_key :behavior_histories, :care_recipitents
    remove_reference :behavior_histories, :care_recipitent, index: true
  end
end

rails:db:mirateで削除されていることを無事確認。
※remove_foreign_keyとremove_referenceの順序を変えるとうまくいかないため注意してください。

参考:references型のカラムを後から追加・削除

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

routes.rbのルーティングを他ファイルに分割した話

はじめに

本稿は、railsのroutes.rbに記述されたルーティングが数百行にも渡り、コードの可読性が欠けてしまっている場合において、自身がどのように見やすさを確保したのかを記載したものになります。

ルーティングを分割する

0. 編集前のconfig/routes.rb

※記載されている内容は例として設定しているだけなので、それぞれのルーティング名に関連性はありません。

config/routes.rb
Rails.application.routes.draw do
  namespace :api, format: 'json' do
    namespace :v1 do
      namespace :libraies do
        resources :libraries
      end

      namespace :books do
        resources :books
      end
    end

    namespace :v2 do
      namespace :libraries do
        resources :libraries do
          collection do
            get :borrowed_books
          end
        end

        resources :librarians do
        end
      end
    end
  end
end

こちらのroutes.rbをファイル分割していきます。

1. config配下に"routes"ディレクトリを作成する

スクリーンショット 2021-03-07 21.43.56.png

2. config/routes.rbを編集

config/routes.rb
Rails.application.routes.draw do
  def draw(routes_name)
    instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb")))
  end

  Rails.application.routes.draw do
   # ここに"routes_name"を定義します。
  end
end
  • "routes_name"には1. config配下に"routes"ディレクトリを作成するで作成したroutesディレクトリ内に作成したファイル名を指定します。

※まだconfig/routes配下にファイルを作成していないので、次で作成していきます。

分割先のファイルを用意

今回サンプルで作成したroutes.rbでは

  • api v1
  • api v2

この粒度で分割していきたいと思います。

そこでapi_v1.rbapi_v2.rbという名前のファイルを作成します。

touch config/routes/api_v1.rb
touch config/routes/api_v2.rb

3. routes.rbに作成したファイル名を指定する

config/routes.rb
Rails.application.routes.draw do
  def draw(routes_name)
    instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb")))
  end

  Rails.application.routes.draw do
    draw :api_v1
    draw :api_v2
  end
end

4. api_v1.rbを編集

config/routes/api_v1.rb
namespace :api, format: 'json' do
  namespace :v1 do
    namespace :libraies do
      resources :libraries
    end

    namespace :books do
      resources :books
    end
  end
end

5. api_v2を編集

config/routes/api_v2.rb
namespace :api, format: 'json' do
  namespace :v2 do
    namespace :libraries do
      resources :libraries do
        collection do
          get :borrowed_books
        end
      end

      resources :librarians do
      end
    end
  end
end

終わりに

これでかなり見やすくなりました!

分割するタイミングはプロダクトによって様々だと思いますが、コードの可読性を上げるためにも管理ができる範囲でコードをどんどん外に出していくことは重要だと思います。

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

[Ruby] あるインスタンスのブロック内で、そのインスタンスのメソッドやインスタンス変数を使う方法

instance_eval を使って実現

Ruby 3.0.0

Code

class Klass 
  def initialize &block 
    @instance_variable 
    instance_eval(&block) unless block.nil? 
  end 
  def instance_method 
    p @instance_variable 
  end 
end 

Using

klass_instance = Klass.new do 
  @instance_variable = :hoge 
  instance_method 
end 

Result

:hoge 

Reference

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

【個人開発】ファラオが情報共有し現代社会を生き抜くアプリ作りました

はじめに

こんにちはファラオ
世は情報社会、多くの人々がスマートフォンを持ち歩きTwitterなどで常に情報交換していますね
しかし、世で使われているSNSは現代の言語で書かれており、ファラオが使うのには少し不便なのではないでしょうか
今回はそんな情報収集に苦労しているファラオ達に使っていただけるSNSを開設しました

サービス概要

hieroglitter
ファラオ達がヒエログリフで情報を共有出来るアプリです
普段お使いの言語を入力すると、ヒエログリフに変換されて投稿されます
これで世界中のファラオと情報を共有出来ますね
※現在は日本語とアルファベットのみに対応しています

使い方

こちらがトップページです
image.png
ファラオのプライバシーを守るためファラオログイン機能によって平民はフィルタリングしています
安心してお使いください
ファラオの皆さんならどこからファラオログイン出来るか一目瞭然ですね?
こちらがアプリのメインページです
image.png
ここから好きなように投稿してください
Image from Gyazo
お気に入りの投稿を見つけたら猫ボタンを押すと好きなだけあなたの好意を伝えられます
Image from Gyazo

使用技術について

自作gemについて

アルファベットを対応するヒエログリフに変換して出力するメソッドを提供する単純なgemです
Unicodeはヒエログリフに対応しているので、A-Zまでそれぞれの音が対応するヒエログリフの文字コードをもとに文字列を変換しています
アルファベット以外はそのままの文字を出力します
※デプロイ後にノリで作成したのでせっかくなのでノリで使いました

おわりに

クソアプリ作ってみたかったので心が満たされました
自分の好きな感じのアプリが作れたかなと思います
就活用に作ってるポートフォリオそっちのけで作ったので、そっちが全然進んでませんが僕は元気です
よかったら遊んでみてくださいね
hieroglitter
ちなみにファラオファラオ言ってたらアプリの名前誰も覚えてくれなくてファラオSNSって呼ばれてます
このアプリはweb1week-202102に参加するために作りました。だら?FlutterとReactとLaravelと風来のシレン様、良い機会を下さりありがとうございました

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