20210322のRailsに関する記事は20件です。

フォームに入力した値の重複の確認

写真のようにフォームに入力した値に重複があるのかを、保存前に確認する方法を見つけることができたので記録します。

スクリーンショット 2021-03-22 21.54.52.jpg

ポジションは、f.selectを使用して、選択した値に対応する数字が、
選手名は、f.collection_selectを使用して、対応する選手ID(player.id)が送られるようにしています。

フォームから送られるparamsは、以下のようにタイトル、ポジション、名前が存在します。
(starting,relief,closerは、対応する投手の選手IDが入っています。)
今回は、ポジション選手名に重複がないかを確認します。

{"title"=>"サンプル1",
 "one_position"=>"6", "one_name"=>"11",
 "two_position"=>"4", "two_name"=>"11",
 "three_position"=>"4", "three_name"=>"5",
 "four_position"=>"5", "four_name"=>"14",
 "five_position"=>"3", "five_name"=>"27",
 "six_position"=>"7", "six_name"=>"12",
 "seven_position"=>"9", "seven_name"=>"17",
 "eight_position"=>"8", "eight_name"=>"3",
 "nine_position"=>"2", "nine_name"=>"19",
 "starting"=>"20", "relief"=>"21", "closer"=>"22"}

手順

1.処理しやすいようにポジション選手名を別々の配列に振り分ける
・ 交互にポジションと選手名に振り分けたいが、starting,relief,closerと連続で選手のIDが入っているので、一旦切り分ける
2.ポジションと選手名を分ける。
3.重複の確認

処理しやすいように分ける

field_player = params[:best_nine].values[1..-4] #タイトルと投手情報以外をいれる
pitcher = params[:best_nine].values[-3..-1] #投手の選手IDをいれる

ポジションと選手を分ける

each_slice(n)で、n要素ずつブロックに渡してを繰り返す。
・野手のポジションと選手IDがブロックに渡される([[one_position, one_name],[two_position, two_name], ・・・])
・ブロックの1つ目をポジション、最後(2つ目)を選手名として扱う
Ruby 3.0.0 リファレンスマニュアル
playerには、投手の選手IDを追加する。
・配列に配列をpushで挿入すると、二重配列のような形になるので、flattenを用いて平坦化する
position = field_player.each_slice(2).map(&:first)
# field_player.each_slice(2).map {|n| n.first} と同じ処理
player = field_player.each_slice(2).map(&:last).push(pitcher)
player.flatten!

重複がないか確認

配列の要素の数と、配列内で重複していない要素の数の差が0でなければ、重複した値があると判定します。

if ((position.count - position.uniq.count) != 0 || (player.count - player.uniq.count) != 0)

全てのコード

field_player = params[:best_nine].values[1..-4]
pitcher = params[:best_nine].values[-3..-1]

position = field_player.each_slice(2).map(&:first).push(pitcher) # {|n| n.first}
position.flatten!
player = field_player.each_slice(2).map(&:last)

if ((position.count - position.uniq.count) != 0 || (player.count - player.uniq.count) != 0)

参考記事

Ruby | 配列の奇数番目と偶数番目を取り出す方法
Ruby 3.0.0 リファレンスマニュアル (※each_slice)
Rubyで複数の配列を1つの配列に結合するために色々やってみた
重複した要素を取り除く

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

【超かんたん】Railsのscopeってなんぞや?

scopeの使い方をレシピアプリを例に解説、設定していきます。

scopeとは

そもそもscopeとは何なのか? Railsガイドでは以下のように説明されています。

スコープを設定することで、関連オブジェクトやモデルへのメソッド呼び出しとして参照される、よく使用されるクエリを指定することができます。スコープでは、where、joins、includesなど、これまでに登場したすべてのメソッドを使用できます。どのスコープメソッドも、常にActiveRecord::Relationオブジェクトを返します。このオブジェクトに対して、別のスコープを含む他のメソッド呼び出しを行なうこともできます。
単純なスコープを設定するには、クラスの内部でscopeメソッドを使用し、スコープが呼び出されたときに実行して欲しいクエリをそこで渡します。
出典:Railsガイド

むむ、なんか難しそう...
と思われる方も多いかもしれませんが簡潔にまとめるとscopeとはwhere、order、limitなどの複数のクエリをまとめたメソッドになります。

文章だけでは難しく感じると思うので実際にコードを見ていきましょう。

scopeの使い方の例

例えば以下のようにしてレシピ情報を取得するとします。

app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
...
  def index
    @recipes = Recipe.order(created_at: :desc).limit(10)
  end
...
end

上記のコードのorder(created_at: :desc).limit(10)の部分をrecentと命名しモデルにまとめていきます。

app/controllers/recipes_controller.rb
class Recipe < ApplicationRecord
...
  scope :recent, -> {order(created_at: :desc).limit(10}
...
end

上記で定義したscopeを使用します。

app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
...
  def index
    @recipes = Recipe.recent
  end
...
end

以上が使い方の例になります。

scopeのメリット

scopeを使うことによって以下のようなメリットがあります。
・ コードが簡潔になる
・ 修正する場合修正箇所が少なくなる
・ 自分で命名できるので直感的に扱いやすくなる

先ほどの例で説明すると以下のコードは同じ意味になりますが、scopeを使用した方がより簡潔になります。

app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
...
  def index
    #scopeを定義しなかった場合
    @recipes = Recipe.order(created_at: :desc).limit(10)
    #scopeを定義した場合
    @recipes = Recipe.recent
  end
...
end

scopeの設定方法

scopeがどんなものか理解できてきたと思うので、設定の仕方について説明していきます。

基本的な設定方法

class モデル名 < ApplicationRecord
  scope :スコープ名, -> { 条件式 }
end

第一引数にシンボルを使ってスコープ名設定し、第二引数は-> { 条件式 }とします。
先ほどの例でいうと条件式はorder(created_at: :desc).limit(10)になります。

引数を使う場合

->のあとに引数を定義すると引数を渡すことができます。

class モデル名 < ApplicationRecord
  scope :スコープ名, -> (引数) { 条件式 }
end

先ほどの例で説明するとlimitで取得したい数を引数にします。

app/controllers/recipes_controller.rb
class Recipe < ApplicationRecord
...
  scope :recent, -> (count) {order(created_at: :desc).limit(count)}
...
end
app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
...
  def index
    @recipes = Recipe.recent(10)
  end
...
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

解決!Rubyのバージョンアップに伴うトラブル|rails コマンドが動かない

1. 状況

1) 開発環境にあったプロジェクトをherokuにpushしようとしたところ、ruby のバージョンが 2.6.3 になっているけど、2.6.6 を使用しろと言われた。

2) これは、なんとか解決。その時の戦記はこちら

3) 無事にherokuにpushはできた。

4) しかし、そのあと、rails s しようとしたところ、謎のエラーが出た。

環境
% ruby -v
ruby 2.6.6p146 (2020-03-31 revision 67876) [x86_64-darwin20]

% rbenv versions
  system
  2.5.1
  2.6.3
* 2.6.6 (set by /Users/randytozuka/.rbenv/version)

% gem -v
3.2.15

エラー文

ターミナル
% rails s            
/Users/randytozuka/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/bundler-1.17.2/lib/bundler/rubygems_integration.rb:200: warning: constant Gem::ConfigMap is deprecated
Traceback (most recent call last):
    71: from bin/rails:3:in `<main>'
    70: from bin/rails:3:in `load'
    69: from /Users/randytozuka/Desktop/deskschedule2-master/bin/spring:15:in `<top (required)>'
  (中略)
    2: from /Users/randytozuka/Desktop/deskschedule2-master/vendor/bundle/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/loaded_features_index.rb:89:in `register'
   1: from /Users/randytozuka/Desktop/deskschedule2-master/vendor/bundle/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/Users/randytozuka/Desktop/deskschedule2-master/vendor/bundle/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require': cannot load such file -- etc (LoadError)

2. 対応

ググってみたところ、先人がこのようなアドバイスを残してくれていた。
・bundle install をしろ
・gem install bundler を試してみろ
・gem を update したら bundler も update せよ
・RubyGemsのバージョンをupdate か downgradeして、bundlerのバージョンと相性のよいバージョンに合わせろ

ターミナル
% gem update --system
Latest version already installed. Done.

% gem update bundler
Updating installed gems
Nothing to update

% gem list rubygems-update
*** LOCAL GEMS ***
rubygems-update (3.2.15)

状況変わらず…(TT)
ここでメンターさんから、これやってみと教えていただいた事をやってみる。

その1
gem uninstall -I -a -x --user-install --force
ERROR:  While executing gem ... (Errno::ENOTEMPTY)
    Directory not empty @ dir_s_rmdir - /Users/randytozuka/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/rails-6.1.3

エラーか強い心でスルーする。

その2
gem install bundler
Fetching bundler-2.2.15.gem
Successfully installed bundler-2.2.15
1 gem installed

その3、ここでGemfile.lock を削除して,

その4
% bundle install
% rails s 

どうじゃ!! お?? エラーがちょっと変わった。
ん〜〜〜となったところで,たまたま見た記事 にあった ↓ を試してみる。

ターミナル
% bundle install --redownload

解決! やった!

3. まとめ

丸一日彷徨いました。
今回のトラブルは、ruby 及び rails のバージョンを上げたが、gem がそれについてきていなかったので、もう一回最新の gem のライブラリを読み込んだら解決した…ということだったのかなと。安易にrubyのバージョンあげると大変だよということでいい勉強させてもらいました。

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

【Rails】投稿したURLをリンク可能にする方法

投稿したURLをリンクにしたい!!!

結論からいうと、Gemをインストールすれば簡単に解決できます!

イメージが湧きやすいように実際の画像で説明します。

①このように製作した投稿機能からURLを投稿すると...
スクリーンショット 2021-03-22 18.01.19.png

↓↓↓↓↓↓

スクリーンショット 2021-03-22 18.14.27.png

②このような感じでリンクが押せない状態で投稿されてしまう。

rinku というGemをインストールすれば問題は解決できる。

③Gemfileにrinkuを追加。

gem 'rinku'
bundle install

これでrinkuをインストールできました。

viewのコードをrinkuに対応した形式に書き換える。

④erbファイルを、rinkuに対応したコードに書き換えます。

<%= post.link %>

↓↓↓↓↓

<%= Rinku.auto_link(post.link, :all, 'target="_blank"').html_safe %>

これだけでリンクとして反映されています。

ただこれだけだと、もしもリンクを投稿しない場合、「データがnilですよ」というエラーが発生する可能性があります。
そんな時はlinkの投稿フォームにあらかじめバリデーションをかけておくか、

<% if post.link.nil? %>
  <%= post.link %>
<% else %>
  <%= Rinku.auto_link(post.link, :all, 'target="_blank"').html_safe %>
<% end %>

...と、if文でlinkというカラムにデータが存在するか否かを条件分岐してあげれば解決できます。
ちなみに、'target="_blank"'があると外部リンク、無いと内部リンクになるようです。

スクリーンショット 2021-03-22 18.35.10.png

確認してみると、このようにリンクといえばお馴染みの青色が着色されました。
リンクを押せば、外部のサイトにも飛べるようになるはずです。

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

ユーザー管理機能をDeviseを使って実装する方法(Rails)

RailsにDeviseを導入する方法(学習記録):statue_of_liberty:

1: Gemのインストール

まずdeviseというGemをインストールします

# Gemfile
# 追記
gem 'devise

# Gemのインストール
% bundle install

2: rails g devise:installコマンドの実装

# deviseの設定ファイルを作成
% rails g devise:install

3: rails g deviseコマンドでdeviseに管理させたいモデルを生成

# deviseコマンドでUserモデルを作成
% rails g devise user

# マイグレーションファイルも自動生成される

4: マイグレーションの実行

# マイグレーションを実行
% rails db:migrate

5: deviseのビューを作成

※ deviseでログイン機能を実装すると、ログイン/サインアップ画面が自動的に生成されますがビューファイルとしては生成されません。これは、deviseのGem内に存在するビューファイルを読み込んでいるためです。deviseのビューファイルに変更を加えるためには、deviseのコマンドを利用して、ビューファイルを生成する必要があります。

% rails g devise:views

# 生成されたビューファイルを独自に編集

5: カラムの追加

※ 現在、サインアップ時に登録する情報はメールアドレスとパスワードの2つです。これに加えて名前を登録できるようにしましょう。
テーブルにカラムを追加するために、マイグレーションを生成する必要があります。

1.rails g migrationコマンド

マイグレーションを生成するコマンドです。
マイグレーションはこれまで、rails g modelコマンドでモデルと一緒に生成されていましたが、すでに作成されたテーブルの内容を変更する際などに使用します。

2.usersテーブルにnameカラムをstring型で追加
# usersテーブルにnicknameカラムをstring型で追加するマイグレーションファイルを作成
% rails g migration AddNameToUsers name:string

# 作成したマイグレーションを実行
% rails db:migrate
3.ストロングパラメーターを使えるようにする

※ サインアップ時に入力する情報はパラメーターとしてサーバーに送信されます。deviseを使わない通常のリクエストの場合は、コントローラーにストロングパラメーターを記述し、受け取れるパラメーターを制限していました。deviseに関しても、同様にストロングパラメーターをコントローラーに記述します。しかし、deviseの処理を行うコントローラーはGem内に記述されているため、編集することができません。

4.devise_parameter_sanitizerメソッド

deviseにおけるparamsのようなメソッドです。deviseのUserモデルに関わる「ログイン」「新規登録」などのリクエストからパラメーターを取得できます

# application_controller.rbを編集
# sign_up(新規登録の処理)に対して

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

  private
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
  end
end

以上がユーザー管理機能をDeviseを使って実装する方法です!参考までにどうぞ!!

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

【Rails】credentials.yml を使って管理画面を作る(その1)

■ はじめに

皆さん、こんにちは。
コロナの影響もあって外出するのも、スティホームするのも大変ですよね。

さて、皆さんは管理機能ってどうされてます?
'rails_admin'などのgemを使ったり、独自に同様の機能を実装されてたりしていますか?

私もそうしたgemを使ったことがあるのですが、特定のカラムに特定の値が入っているかどうかで、管理画面へのアクセスにできてしまうことに、ちょっと怖さを感じてしまうことがあります(汗)

そこで、特定の管理者にのみ完全な管理画面へのアクセスを許可し、その管理者から一部のアクセス権を付与するのはどうだろうかと考えました。

(*以下、アクセス権を付与できる者を管理者、管理者からアクセス権を付与されたユーザーをパートナーとします。)

具体的には、
- 管理者のみ、完全な管理画面へのアクセスを可能にする。
- その管理者はアクセス権を他者に付与することができる。
- アクセス権を付与されたパートナーは管理画面の一部にアクセスすることができ、一部の管理機能を使うこともできる。
- 管理者でもなく、アクセス権を持っていない一般ユーザーは管理画面にアクセスできない。

上記の形であれば、仮にパートナーのIDが第三者に乗っ取られてしまっても、被害をある程度限定することができるし、管理者がアクセス権を早急に剥奪してしまえば、事態の収束も早くできるかと考えました。

今回のテストアプリケーションは、そうした考えを元に管理画面の分岐を自前で実装したものになります。そのため注力していない部分はあっさりしておりますが、温かい目で見て頂けると嬉しいです。また、ご意見やご感想、セキュリティ部分でのご指摘等々頂けると幸いです。


■ 本記事での試み

本記事ではRils5.2から追加された「credentials.yml」を使って、特定のメールアドレスからログインしたユーザーを管理者として扱い、管理画面を作成します。

管理画面は管理者のみアクセス可能で、一般ユーザーがアクセスを試みるとトップページヘリダイレクトするようにします。

*管理者がパートナーにアクセス権を付与する機能の実装は別記事にて記載します。

[DEMO]

下の動画では、左側が管理者でログインした場合、右側はそれ以外でログインした場合になります。管理者にはヘッダーの部分に'管理画面'が表示されていますが、一般ユーザーの方には表示されていません。また、一般ユーザーが管理画面にアクセスすると、「アクセスをブロックしました」というメッセージ表記と共にトップページが表示されます。
Image from Gyazo


■ 開発環境

  • OS:macOS Big Sur 11.2.3
  • Ruby:3.0.0
  • Ruby on Rails:6.1.3
  • ローカル環境DB:Mysql
  • テキストエディタ:Visual Studio Code ***

■ アプリの立ち上げからデータベースの作成まで

まずは rails new を使ってアプリケーションを作成しましょう。今回はmysqlをデータベースに使用しますが、データベースはお好みのものをお使いください。

$ rails new access_test -d mysql
$ cd access_test

(以降の作業でmysqlのエンコードがuft8mb4だとエラーが起きてしまうので、rails db:createを行う前に config/database.yml を編集します。仮に先に db:create を行ってしまった場合は、db:drop で一度データベースを破棄してください。)

config/database.yml
encoding: uft8    # utf8mb4から変更

database.ymlを変更したら、 rails db:create でデータベースを作成します。

$ rails db:create

■ gem 'devise'の導入から rails db:migrateまで

今回はgem 'devise'を利用してログイン機能を実装します。
まずはGemfileの最下部にgem 'devise'を追記します。

gem 'devise'

次にターミナルで bundle install、rails g devise:install、rails g devise User を実行します。

$ bundle install
$ rails g devise:install
$ rails g devise User

- 参考記事 -

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

(*)今回は仮のメールアドレスを使用するためメール認証を実装しませんが、devise のメール認証を実装すると、仮に管理者用に設定したメールアドレスが特定されてしまったとしても、認証で送られてきたメールを開いて認証行動を行わない限り、そのメールアドレスはサービス内で利用できない状態を保てます。その後は管理者用に設定するメールアドレスを変更したり、一定回数ログインに失敗したアカウントをロックしたりすることで、安全性を保った状態でサービスを運用できるかと思います。

参考記事

(1)Rails deviseによるユーザー認証 メールによる認証、emailとusernameのどちらでもログイン可能にするまで

(2)【Rails】メール送信設定 〜gmail利用〜

(3)How To: Add :lockable to Users

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

次に一般ユーザーが利用できる架空のサービスとして、Post機能を scaffold を使って導入していきましょう。

$ rails g scaffold Post text:text user:references

上記のコマンドでPost機能の実装はだいぶ進みましたね。
ここから先はいろいろなファイルの中身に触れていくので、焦らずじっくりいきましょう。

まずは、devise による「ユーザーがログインしているかどうかを判別する機能」をアプリケーション全体で機能させるために、application_controller.rb にauthenticate_user! メソッドをbefore_actionで追記します。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :authenticate_user!
end

次に、ユーザーを削除した際にそのユーザーが投稿したものが残ってしまうことがないように、PostモデルとUserモデルの関係性を記述します。

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :posts, dependent: :destroy
end
app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end

(今回はUser(主)がいくつものPost(従)を持てる関係のため、Userモデルに has_many :posts、Postモデルに belongs_to :user としています。その上でUserが削除された際に、そのUserが投稿していたPostも全て削除されるように、「 has_many :posts, dependent: :destroy 」としています。)

これでモデルの関係性も構築できたので、以下のコマンドを実行してマイグレーションファイルをupしましょう。

$ rails db:migrate

■ ルーティングの設定からPost機能実装まで

さて、マイグレーションファイルも up しましたし、今度はサインアップを行うためのルーティングの整備だったり、投稿を行うための諸設定を整えていきましょう。

まずはconfig/routes.rbの中身を以下のように変更します。

config/routes.rb
Rails.application.routes.draw do
  root to:    "posts#index"
  devise_for  :users
  resources   :posts
end

次に、scaffold で作成した諸々のファイルを整備していきましょう。手始めにposts_controller.rb内の post_params を以下のように書き換えます。

app/controllers/posts_controller.rb
def post_params
  params.require(:post).permit(:text).merge(user_id: current_user.id)
end

今度はviewファイルの編集です。application.html.erbを開いて、header 部分の実装、その header にログイン関係のリンクを実装しましょう。( devise による user_signed_in? でユーザーがログイン状態にあるかないかを判別しています。)

app/views/layouts/application.html.erb
<body>
  <header>
    <div>
      <% if user_signed_in? %>
        <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
      <% else %>
        <%= link_to 'サインアップ', new_user_registration_path %> |
        <%= link_to 'ログイン', new_user_session_path %>
      <% end %>
    </div>
  </header>
  <p class="message"><%= flash[:message] %><p>
  <p class="alert"><%= alert %></p>
  <%= yield %>
</body>

今のままだと header 部分も真っ白になってしまうので、ちょっと cssファイルで headerに色をつけましょう。(今回は安直にapplication.css の最下部に header の背景カラーを追加します。)

app/assets/stylesheets/application.css
header {
  background-color: lightgray;
}

次に _form.html.erb に残っている下記の部分を削除します。

app/views/posts/_form.html.erb
<!-- ここから以下の箇所までを削除 -->
  <div class="field">
    <%= form.label :user_id %>
    <%= form.text_field :user_id %>
  </div>
<!-- 以上の箇所からここまでを削除 -->

続いて、誰が投稿したのかを分かりやすくするために、今回はEmailを表示させるようにしましょう。(本番のサービスではEmailではなく、name等を表示させるようにしてくださいね。)

app/views/posts/index.html.erb
<table>
  <thead>
    <tr>
      <th>Text</th>
      <th>User_Email</th>   <!-- 変更-->
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.text %></td>
        <td><%= post.user.email %></td>   <!-- 変更-->
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>
app/views/posts/show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>

<p>
  <strong>User_Email:</strong>   <!-- 変更-->
  <%= @post.user.email %>   <!-- 変更-->
</p>

<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>


■ credentials.ymel.encの編集から管理画面のコントローラー実装まで

ここまでの作業お疲れさまでした。
さて、本記事の肝心要の部分に入っていきます。
先程よりも手を動かす箇所が多いので、焦らずゆっくり進めていきましょう!

まずは credentials の中身を変更するためにコマンドを入力しましょう。

$ EDITOR='code --wait' rails credentials:edit

(私はVSCodeを利用しているため、上記でcredentialsの編集画面が表示されますが、他のエディタを利用されている方は以下の参考記事を元にcredentialsの編集画面を開きましょう。)

- 参考記事 -

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

参考記事
(4)Rails5.2のrails credentials:editを好きなエディタで編集する(macOS)

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

credentials の中身に今回の管理者用のメールアドレス(test@test.com)を、キー(access_user:)と共に追加します。追加後、credentials.yml.encファイルを閉じると変更内容が保存され、以降「 Rails.application.credentials[:access_user] 」で test@test.com を呼び出すことが可能になります。

(config/master.key の情報が外部に漏れなければ、他者が credentials.yml の中身を見ることはできません。仮にアプリケーションのコードがGitHub上に公開されてしまったとしても、master.key の情報が無ければ、管理者用として設定されているメールアドレスがなんであるかはコードを見ただけでは特定されることはありません。)

credentials.yml.enc
# aws:
#   access_key_id: 123
#   secret_access_key: 345
.....
  <以下を追加>
access_user: test@test.com
.....

次に管理画面へのルーティングを設定します。今回はUsersコントローラーを利用して、管理画面の表示とユーザーの削除を考えているので、以下のようにroutes.rbを設定します。

config/routes.rb
Rails.application.routes.draw do
  root to:    "posts#index"
  devise_for  :users
  resources   :posts
  resources   :users, only: [:index, :destroy] # 追加
end

コマンド入力で Users コントローラーを作成します。

$ rails g controller users

続いて、Users コントローラーの中身を記述していきましょう。
(今回は一般画面と管理画面の区別がわかりやすいようにlayoutを指定しています。後ほど、access_layoutのhtml作成と、cssを追記します。)

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :access_user
  layout "access_layout"

  def index
    @users = User.all
    @posts = Post.includes(:user)
  end

  def destroy
    user = User.find(params[:id])
    user.destroy
    flash[:message] = "ユーザーを削除しました"
    redirect_to users_path
  end

  private

  def access_user
    unless current_user.email == Rails.application.credentials[:access_user]
      flash[:message] = "アクセスをブロックしました"
      redirect_to root_path
    end
  end
end

access_user メソッドはログインしているユーザーのメールアドレスと、管理者用に設定したメールアドレス(Rails.application.credentials[:access_user])が一致しなかった場合、flash[:message]に"アクセスをブロックしました"という文字列を代入して、トップページヘリダイレクトさせます。

(本番のサービスではリダイレクトではなく、何もないURLにアクセスを試みた時と同様のエラーを返す仕組みにすれば、悪意あるユーザーに管理画面へのURLを知られずに済むかと思います。)

これで大まかな機能の実装は完了です。もう少しでこの記事の作業も終わりますので、あと一息頑張りましょう!


■ 管理画面の view ファイルの作成から css の設定まで

ここまでの作業お疲れさまです。本記事も残すところあと僅かです!一緒に頑張りましょう!

まずは、application.html.erb の header 部分に管理画面へのリンクを追加します。

app/views/layouts/application.html.erb
<header>
  <div>
    <% if user_signed_in? %>
      <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
      <% if current_user.email == Rails.application.credentials[:access_user] %>
        ||| <%= link_to '管理画面', users_path %>
      <% end %>
    <% else %>
      <%= link_to 'サインアップ', new_user_registration_path %> |
      <%= link_to 'ログイン', new_user_session_path %>
    <% end %>
  </div>
</header>
if current_user.email == Rails.application.credentials[:access_user] の記述で、ログインしているユーザーのメールアドレスとcredentials.ymelに設定したメールアドレスが一致していたら、'管理画面'のリンクが現れるようにしています。

次に先程作成した管理画面のリンク先であるindex.html.erbを、app/views/usersファイル内に手動で作成して以下のように記載します。(*この時点でrails s を実行して管理画面にアクセスしても、access_layout.html.erbをまだ作成していないためエラーになります。)

app/views/users/index.html.erb
<h1>管理画面</h1>

<table>==== ユーザー一覧 ====
  <thead>
    <tr>
      <th>ユーザーID</th>
      <th>メールアドレス</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @users.each do |user| %>
      <tr>
        <td><%= user.id %></td>
        <td><%= user.email %></td>
        <td><%= link_to 'Destroy', user_path(user), method: :delete, data: { confirm: 'ユーザーを本当に削除しますか?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<table>==== 投稿一覧 ====
  <thead>
    <tr>
      <th>投稿ID</th>
      <th>投稿内容</th>
      <th>投稿者ID</th>
      <th>投稿者メールアドレス</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.id %></td>
        <td><%= post.text %></td>
        <td><%= post.user_id %></td>
        <td><%= post.user.email %></td>
        <td><%= link_to 'Destroy', post_path(post), method: :delete, data: { confirm: 'この投稿を本当に削除しますか?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to '一般トップページへ', root_path %>

続いて、access_layout.html.erb を、 app/views/layouts ファイル内に手動で作成して以下のように記載します。

app/views/layouts/access_layouts.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>管理画面</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body class='access'>
    <header>
      <div>
        <% if user_signed_in? %>
          <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
          <% if current_user.email == Rails.application.credentials[:access_user] %>
            ||| <%= link_to '管理画面', users_path %>
          <% end %>
        <% else %>
          <%= link_to 'サインアップ', new_user_registration_path %> |
          <%= link_to 'ログイン', new_user_session_path %>
        <% end %>
      </div>
    </header>
    <p class="message"><%= flash[:message] %><p>
    <p class="alert"><%= alert %></p>
    <%= yield %>
  </body>
</html>

そして、管理画面のみブラックモードのような画面にして、一般画面との違いが分かりやすくしましょう。(今回は安直にapplication.cssの最下部に以下を追記します。)

app/assets/stylesheets/application.css
.access {
  background-color: black;
  color: white;
}

a {
  color: lightgray;
}

■ サーバーの起動と実装した部分の確認

作業お疲れさまでした。
これで管理者と一般ユーザーのアクセスできる部分を分けることができました。実際に rails s コマンドを実行してサーバーを立ち上げて確認してみてください。

$ rails s
  • application.credentials[:access_user]に設定したメールアドレスでログインした場合と、そうでない場合でヘッダーに'管理画面'の表示の有無が確認できたでしょうか?

  • また、application.credentials[:access_user]に設定したメールアドレス以外でログインした場合、 http://localhost:3000/users にアクセスするとどうなりましたか?

[DEMO]

下の動画では、左側が管理者でログインした場合、右側はそれ以外でログインした場合になります。管理者にはヘッダーの部分に'管理画面'が表示されていますが、一般ユーザーの方には表示されていません。また、一般ユーザーのブラウザで http://localhost:3000/users にアクセスすると、「アクセスをブロックしました」というメッセージ表記と共にトップページが表示されていますね。
Image from Gyazo


■ 本記事の最後に

本記事は以上になります。お疲れさまでした。

冒頭のはじめにで記載しました管理者からパートナーへのアクセス権の付与や、パートナーの限定的管理機能の実装につきましては、別の記事にて記載したいと思います。

本記事で作成したアプリケーションのコードは、以下のGitHub上から閲覧できますので適宜ご活用ください。

最後に

ここまで読んでいただき、ありがとうございました!!

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

Rails API 設定

[API 設定方法]

通常のrails newコマンドの末尾に--apiをつけることでAPIモードでアプリを作成することができる。
(APIに必要ない部分をデフォルトで作成しなくなる。)

--apiをつけない場合

config/application.rbのApplicationクラス定義の冒頭に、次を追加します。

config.api_only = true

app/controllers/application_controller.rbの以下のコードを置き換えます。

class ApplicationController < ActionController::Base
end

上を以下に変更します。

class ApplicationController < ActionController::API
end

routing分割方法

rails6.1からroutes.rbのファイルを複数に分けることができるようになった。

  • ファイル分割する際のディレクトリ構成
  ├─ routes.rb
  │
  └─ routes/ #ここに分割したroutesファイルを格納
     ├─ api.rb
     └─ admin.rb
  • routes.rbの記載を変更

draw(:読み込みたいファイル名)

 Rails.application.routes.draw do

  draw(:api) #別ファイルのルーティングを読み込む
  draw(:admmin) #別ファイルのルーティングを読み込む
end
  • routesディレクトリ&外部ファイルの作成

以下のディレクトリ構成になるようにroutesディレクトリと外部ファイルを作成します。

config
  ├─ routes.rb
  │
  └─ routes/ #このディレクトリを作成
      ├─ api.rb    #外部ファイルを作成
      └─ admin.rb  #外部ファイルを作成
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-compose Rails + Postgresql環境構築

作業ディレクトリと必要なファイルを作成

まず開発していくディレクトリとアプリケーションを構築するために必要なファイルを作成する

必要なファイル


Dockerfile
Gemfile
Gemfile.lock
entrypoint.sh
docker-compose.yml
$ mkdir myapp
$ cd myapp
$ touch {Dockerfile,Gemfile,Gemfile.lock,entrypoint.sh,docker-compose.yml}
$ ls
Dockerfile      Gemfile.lock        entrypoint.sh
Gemfile         docker-compose.yml

ファイルの中身を記述してく

Rails6ではwebpackerが搭載されyarnのインストールが必要

Dockerfile
FROM ruby:2.6.6

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarn
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
Gemfile
source 'https://rubygems.org'
gem 'rails', '6.0.3'
entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=password
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

プロジェクトの構築

作成したファイルを利用してdocker-compose runを実行しアプリケーションを生成

$ docker-compose run web rails new . --force --no-deps --database=postgresql

そして

$ docker-compose build
.
.
.
Successfully built 0b14a3eedc73
Successfully tagged myapp_web:latest

imageを構築

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

【Ruby on Rails6.0】データベースを指定してアプリ作成

前提

■あくまで個人の備忘録。見やすは非重視
■PostgreSQLが既にインストールされていること
※されていない場合は以下でインストールとPath設定を完了させる
PostgreSQL インストール方法

Rubyインストール

★以下のサイトからWITH DEVKITの推奨版をダウンロード
※サイト内左側の一覧に「=>」となっているものが推奨

Ruby公式サイト 

★ダウンロードしたファイルで設定を進める
※途中コマンドプロンプトにて入力を求められた場合には、各々指定が無ければ「1,2,3」と入力

★ダウンロードが完了したら、コマンドプロンプトを起動し、バージョンを確認し正常に確認出来たらインストールされている

# コマンドプロンプト
$ ruby -v
# 出力結果
ruby 2.7.2p137(2020-10-01 reversion 5445e04352) [x64-mingw32]

※「$」は入力不要

Railsインストール

■以下のコードを実行し、Railsをインストールする

# コマンドプロンプト
$ gem install rails

■Railsがインストールされたかを確認するために、バージョンを確認

# コマンドプロンプト
$ rails -v
# 出力結果
Rails 6.1.3

Yarnインストール

★以下のコードを実行し、Yarnをインストールする

# コマンドプロンプト
$ npm install --global yarn

★Yarnがインストールされたかを確認するためにバージョンを確認

# コマンドプロンプト
$ yarn -v
# 出力結果
1.22.10

Node.jsインストール

■以下サイトでNode.jsをダウンロード・インストールを実行

Node.js

■システム詳細設定内の環境変数(ユーザ環境変数)にPathを設定

デフォルトの場合の設定:C:\Program Files\Nodejs

■Node.jsがインストールされたかを確認するためにバージョンを確認

# コマンドプロンプト
$ node -v
# 出力結果
v14.16.0

アプリの作成

★コマンドプロンプトで以下のコードを実行

# コマンドプロンプト
$ rails new (アプリ名) -d postgresql

★アプリケーション毎にGemバージョンを管理する場合は以下を実行

# コマンドプロンプト
$ rails new (アプリケーション名) -d postgresql --skip-bundle

★インストール完了後にアプリケーションディレクトリに移動し、以下のコードを実行

# コマンドプロンプト
# 初回のみ以下
$ bundle install --path vendor/bundle

# 2回目以降
$ bundle install

データベースの連携

■psql.exe が保存されたディレクトリをPatchに設定

デフォルトの場合の設定:C¥:Program Files¥PostgreSQL¥(バージョン)\bin

■作成したアプリケーションの以下のファイルを編集
ルート:(アプリケーション名)\config\database.yml

#省略

default: &default
adapter: postgresql
encoding: unicode
username: postgres #デフォルトの場合
password: postgres #デフォルトの場合

#省略

Railsアプリの起動・表示

★cdコマンドでアプリケーションディレクトリに移動

★以下のコードを実行

# コマンドプロンプト
$ rails s

※「Use Ctrl-C to stop」の出力が表示されたらサーバー接続完了

★ブラウザで以下のURLにアクセスし、画像の表示になれば無事設定完了

localhost:3000

qwe.png

★サーバーとの接続を解除する場合は「Use Ctrl-C to stop」と出力された状態のコマンドプロンプトで「Ctrl」+「C」を入力し停止
⇒コマンド入力待機に切り替わったら接続解除完了

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

[ Rails ] turbolinksの概要と一部使い方

RailsでECサイトを作成中に、一部ボタンが動作しないことがあり、turbolinksを一部無効化することで解決しました。
そもそもturbolinksとは何ぞや状態だったので、備忘録として残しておきます。

turbolinksとは

ページ遷移をAjaxに置き換えることで、JavaScriptやCSSのパースを省略することで高速化するgemのこと。
Railsにデフォルトで入っているやつ。

完全にturbolinksを無効化する方法

※ turbolinksを完全無効化すると、他の高速化するgemを入れたりする必要があるので、
私のような初心者は、特別なことがない限りおすすめしないです。

1. gemを無効化する

Gemfile
    #コメントアウトをする
    #gem 'turbolinks'
ターミナル
    $ bundle update

2. application.jsを編集

application.js
    // 変更前
    //= require turbolinks 

    // 変更後
    // require turbolinks 

3. application.html.slimを編集

application.html.slim
    / 変更前
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'

    / 変更後
    = stylesheet_link_tag    'application', media: 'all'
    = javascript_include_tag 'application'

一部を無効化する方法

1. JavaScriptを編集する方法

・.jsファイルの場合

hoge.js
    $(document).on('turbolinks:load', function() {
     // turbolinksを無効化したい処理を入れる
    });

・.coffeeファイルの場合

hoge.coffee
    $(document).on 'turbolinks:load', -> 
      # turbolinksを無効化したい処理

2. リンクを編集する方法

・link_toに追加する場合

hoge.html.slim
    = link_to '', root_path, 'data-turbolinks': false

'data-turbolinks': false をlink_toに追加すれば無効化できます。

・divの場合

hoge.html.slim
    div data-turbolinks='false'
      = link_to '', root_path
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Refile】attachment_image_tag の書き方

attachment_image_tagの書き方

<%= attachment_image_tag 保存先インスタンス名, :保存先カラム名, :fill, 幅, 高さ, format:'拡張子', fallback:"代替画像" %>

例えば、
<%= attachment_image_tag post_image.user, :profile_image, :fill, 100, 100, format:'jpg', fallback:"no_image.jpg"

*fillでうまく表示されない場合は、sizeでサイズを指定
:fill, 幅, 高さsize: "幅x高さ"

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

Refileの保存先をS3に変更する(Rails, AWS)

環境

  • Ruby 2.6.3
  • Rails 5.2.4

[IAM]ユーザー作成

IAMのダッシュボードでユーザーを選択し、ユーザーの追加をクリックします。
ユーザー名を入力AWSアクセスの種類を選択プログラムによるアクセスを選択肢、次へ

スクリーンショット 2021-03-22 12.30.07.png

既存のポリシーを直接アタッチを選択後S3で検索AmazonS3FullAccessを選択し、次へ
ユーザー登録は完了させ、.csvダウンロードする。
access_key secret_access_keyが書いてあるので忘れないように保管する。

(IAM)ユーザーのポリシーの追加

[S3]バケットポリシーの編集

S3に入ってバケットの選択し、アクセス許可のタブをクリック
バケットポリシーを編集するをクリックし、下のコード追加します。
AWSアカウントのidは右上の自分の名前をクリックし、マイアカウントの数字です。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::awsアカウントのid:user/IAMで作成したユーザー名""
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::バケット名/*"
        }
    ]
}

refile.rbを作成

$ touch config/initializers/refile.rb
config/initializers/refile.rb
require 'refile/s3'

#if !Rails.env.production? # 開発環境でS3へアップロードできているか確認する方法

if Rails.env.production? # 本番環境の場合
  aws = {
    access_key_id: ENV['S3_ACCESS_KEY_ID'], # アクセスキーID
    secret_access_key: ENV['S3_SECRET_ACCESS_KEY'], # シークレットアクセスキー
    region: 'ap-northeast-1', # リージョン
    bucket: 'S3バケット名',
  }
  Refile.cache = Refile::S3.new(prefix: 'cache', **aws)
  Refile.store = Refile::S3.new(prefix: 'store', **aws)
end

Refile.cache Refile.store残っていると本番環境で反映されなかったので
エラーになる方は消した方がいいです。

.env
AWS_ACCESS_KEY_ID="アクセスキー"
AWS_SECRET_ACCESS_KEY="シークレットキー"
Gemfile
gem "refile-s3"

bundle installでgemを入れます。
本番環境にも同じように反映させたら完成です。

参考になった記事です
https://qiita.com/matsubishi5/items/c2abdd7375a4c683392a
https://qiita.com/piyor/items/36d2c3c9b8fd638a71a0

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

Rails 検索機能 ransack

ransackを使って検索機能を実装する

gem 'ransack'

ビューファイルに表示するフォームの作成
@pというのはコントローラーで作成するオブジェクトです。
urlについてはコントローラー、ルーティングに記述して、resultアクションを作成。

<%= search_form_for @p, url: result_rooms_path do |f| %>
      <div class="search-select">
            <div class="search-select__container">
              <%= f.label '性別', class: "search-select__container__label" %>
              <%= f.collection_select :user_sex_id_eq, Sex.all, :id, :name, {disabled: 1, include_blank: '指定なし'}, {class: "search-select__container__list"} %>
            </div>

            <div class="search-select__container">
              <%= f.label 'お部屋の場所', class: "search-select__container__label" %>
              <%= f.collection_select :place_id_eq, Place.all, :id, :name, {disabled: 1, include_blank: '指定なし'}, {class: "search-select__container__list"} %>
            </div>
      </div>
<% end %>

collection_selectの中身について

引数 役割
1. メソッド名 :place_id_eq カラム名、name属性やid属性を決める
2.オブジェクト Place.all アクティブハッシュを指定する
3.value :id 表示する際に参照するDBのカラム名
4.name :name 実際に表示されるカラム名
5.オプション include_blank: 何も選択していない時に表示される内容

disabled: 1はその値を無効にするオプションです。
controllerを記述する

 def result
    @rooms = @p.result.includes(:user).limit(20).order(updated_at: "DESC")
 end



 privata
    def result_rooms
    @p = Room.joins(:user).ransack(params[:q])
  end

キー情報である[:q]によってRoomテーブルからデータを探してオブジェクトを生成する。
そしてresultアクション内で@roomsに検索結果を代入している。
.resultで取得することができる。

またresult_roomsでDBからレコードを取得する際に、joinsでテーブル同士を内部結合して検索できるようにしています。
紐づいているユーザー情報からも検索できるようにしています。
そしてresult.html.erbを編集。
コントローラー内で記述した@roomsに対して繰り返し処理を行なって、ビューページに表示をしていきます。

views/rooms/result.html.erb
  <% rooms.each do |room| %>
     <div class="rooms-item__card">
       <%= link_to room_path(room.id), class: "link-cover" do %>
         <h4 class="rooms-item__card__title"><%= room.title %></h4>
         <p class="rooms-item__card__user"><%= room.user.name %></p>
             <div class="rooms-item__img">
               <%= image_tag room.image %>
             </div>
       <% end %>
     </div>
  <% end %>

以上がポートフォリオで作成したransackを用いた検索ページの実装でした。ポートフォリオはまた別記事で公開いたします。
ありがとうございました。

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

コントローラー 単体テスト実装

今回詰まった内容を備忘録のためと、知識定着させるためアウトプット!

現在ポートフォリオで簡単な画像付きの投稿サイトを作成中、コントローラーの単体テストを行っておりました。

indexアクションにリクエストするとレスポンスに投稿済みの画像が存在する

上記テストだけがどうしてもうまくいかず、進まなかった状況です。

開発フレームワークはrailsを使用しており、
RSpec FactoryBot
は導入済みの状態です。

エラー内容

TypeError:
       no implicit conversion of ActiveStorage::Attached::Many into String
     # ./spec/requests/posts_spec.rb:19:in `block (3 levels) in <top (required)>'

ソースコード

posts_spec.rb

equire 'rails_helper'
describe PostsController, type: :request do
  before do
    @post = FactoryBot.create(:post)
  end

  describe 'GET #index' do
    it 'indexアクションにリクエストすると正常にレスポンスが返ってくる' do 
      get posts_path
      expect(response.status).to eq 200
    end
    it 'indexアクションにリクエストするとレスポンスに投稿済みのテキストが存在する' do
      get posts_path
      expect(response.body).to include(@post.name)
    end
    it 'indexアクションにリクエストするとレスポンスに投稿済みの画像が存在する' do
      get posts_path
      expect(response.body).to include("@post.images")
    end
    it 'indexアクションにリクエストするとレスポンスに投稿検索フォームが存在する' do
      get posts_path
      expect(response.body).to include('')
    end
  end
end

posts.rb

FactoryBot.define do
  factory :post do
    name { '投稿名' }
    post_text { '投稿文' }
    association :user

    after(:build) do |post|
      post.images.attach(io: File.open('public/test_image.png'), filename: 'test_image.png')
    end
  end
end

上記の記述で行っていました。
どうしても画像の確認だけができず、つまずいていたという状況でした。

エラーログから考察するに、画像がstring(文字列型)になっているため確認が取れないよと認識する。

そのためbinding.pryを入力してターミナル上で情報の確認
response.body
@post
@post.images
@post.images.blobs

上記全てをターミナル上で確認して、正常に画像がある事を把握する。

画像があるのに対して、string(文字列)になってしまっているエラーのためなぜかを考察する。

そこで、

include("@post.images")

上記の記述の仕方が行けないのではないかと仮説を立てる。

画像情報を取得しているname属性の値を入れてみた。

include("post[images][]")

上記に変更してみたが、同様のstring(文字列)のエラーが出ている。

それでエラーが出るのであれば、直接画像のimg属性のクラス名を指定するやり方で再度実行する

include("item-box-img")

上記コードで出力を行う。

そうしたらテストが成功した。

エラーの原因で文字列になってしまっているというところに着目し、修正を加えて行ったのがエラー解読につながったとみている。

未だ一点だけ不明点があり、
なぜ画像を指定する際に、その画像のname属性ではなく、クラスを指定しなくては行けないのかという点が不明であった。

同じ指定方法をしているのになぜなんだろうと思っている。

参考記事等も見つからず、もしご教授いただけるようでしたら、コメントいただけましたら助かります!

今回はコントローラー単体テストで画像確認のテストをする際におきたエラーでした。

同じようなエラーで迷われている方いましたら、是非ご参考にして頂けましたら幸いです!

宜しくお願いします!

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

テストコードその3

テストコードを効率よく記述するには?

アウトプット用として書いていきます。

FactoryBot

インスタンスをまとめることができるGemのこと。
他のファイルにインスタンスをまとめておいて呼び出すことで使うことができる。

FactoryBotを導入する

前回のrspec-railsと同じようにGemfileにgem 'factory_bot_rails'と記述する。
記述する場所もrspec-railsと同じようにする。
bundle installすれば導入完了。
ちなみに、rspecとFactoryBotは同時にインストールした方がファイルが自動生成されるのでより効率的である。
FactoryBotのファイルは以下のような記述になる。

rb
FactoryBot.define do
  factory :user do
    nickname              {'test'}
  end
end

factoryBotを使ってみる。

設定したインスタンスを生成するためにはbuildを使ってテストコードに記述する。
buildはnewメソッドと同じ意味を持つ。
コードは以下の通り

user=FactoryBot.build(:user)

次にひとつひとつのテストに上記のコードを記述するのは非効率なのでbeforeを使ってインスタンス変数にする。

before do
 @user=FactoryBot.build(:user)
end

ランダムな値を出したいときは。。。

FakerというGemを使う。
記述する場所はFactoryBotと同様である。
これを用いることでメールアドレスや名前などがランダムに生成される。
記述例は以下の通り。
Fakerの後に続くところは決まっている記述なので調べる必要がある。

FactoryBot.define do
  factory :user do
    nickname              {Faker::Name.initials(number: 2)}
    email                 {Faker::Internet.free_email}
    password              {Faker::Internet.password(min_length: 6)}
  end
end

以上です。

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

devise

ウィザード形式のdevise

% rails g devise:controllers users

ルーティング(例)

Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: 'users/registrations'
}
root to: "home#index"

end

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

Bundlerについて

はじめに

初学者です。間違いあればご指摘いただきたいです。

参考

Bundler概要
Bundlerの使い方
Bundlerとは?

Bundlerとは?

  • プロジェクト内で使うRubygemsを管理する仕組みのこと。

  • gemは手動で個別にインストールする事ができる。が、様々なgemを組み合わせて使っていくと「バージョンの組み合わせ」によっては上手くいかないなどの問題が出てくることがある。

  • 複数人、複数環境で開発を行う場合、各々の環境で使うライブラリの名前やバージョンを合わせる必要がある。

  • 上記2点の問題を解決できるのがBundler。
    手動でgemをインストールするよりも、gem同士の互換性を保ってくれるBundlerでインストールする方が便利。

Bundlerのインストール

まず初めに、Bundlerもgemの1つです。
つまり、インストールの必要があるってことですね。

  • Bundlerは手動インストールします。
ターミナル
gem install bundler
  • 導入されたか確認します。バージョンが表示されるかを確認してください。
ターミナル
bundler -v

Bundlerの使い方

  • Gemfileを作る
ターミナル
bundle init

上記のコマンドでGemfileの雛形が作成されます。

  • 使いたいgemをGemfileに記述する  
    • インストールしたいGemパッケージを記述後、bundle installを実行することで、自動的に調べて全部インストールしてくれます!

Bundlerのコマンド

  • bundle init ・・・・Gemfileを生成する
  • bundle install・・・Gemfileに書かれたGemパッケージをインストール
  • bundle exec・・・・Bundlerでインストールされたgemパッケージを使用してコマンドの実行
  • bundle list・・・・・インストール済みのgemパッケージの一覧を表示
  • bundle update・・・インストール済みのgemパッケージのバージョンを更新

Gemパッケージの保存先

bundle installと実行すると、
rbenvを利用していた場合、/Users/ユーザー名/.rbenv/versions/バージョン名/lib/ruby/gems/...に保存されます。

もしこの保存先を変更したい場合、

ターミナル
bundle install --path <フォルダ名>

と打つと、指定したフォルダの下に保存することが可能です。

ちなみに2回目以降は、保存先を記憶してくれているので % bundle install と打つだけで大丈夫です。

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

formの値をコントローラに送る 基本

フォームから送られた値を受け取るには

ルーティングの修正


post "move" => "users#move"

アクションの追加

`
def move

end
`

erbにフォームの送信先を修正


<%= form_tag("/move") do %>
<p>メールアドレス</p>
<input name="email">
<p>パスワード</p>
<input type="password" name="password">
<input type="submit" value="移動">
<%= end %>

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

link_to form_tag form_for の違い

link_to

getのコントローラーを探しに行く

<%= link_to("移動", "/move") → get "move" => "users#move_form"

form_tag

postのコントローラーを探しに行く

<%= form_tag("/move") do %> → post "move" => "users#move"

form_for

<%= form_tag("/move") do %> 
  <p>メールアドレス</p>
  <input name="email">
  <p>パスワード</p>
  <input type="password" name="password">
  <input type="submit" value="移動">
<%= end %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】pluckメソッドのソースコードを読んでみた

アプリケーション作成時に使用した「pluck」メソッドについて、便利な機能だと思っていましたが、どのように処理をしているのか気になったのでRailsのソースコードを読んでみました。

pluckとは

公式ドキュメントでは以下のように記載されています。

pluckは、1つのモデルで使用されているテーブルからカラム (1つでも複数でも可) を取得するクエリを送信するのに使用できます。引数としてカラム名のリストを与えると、指定したカラムの値の配列を、対応するデータ型で返します。

例えば、Productモデルがあったとして、nameカラムの中身を確認したい場合は、

Product.pluck(:name)
=> ["Ruby on Rails Tote",
 "Ruby on Rails Bag",
 "Ruby on Rails Baseball Jersey",
 "Ruby on Rails Jr. Spaghetti",
 "Ruby on Rails Ringer T-Shirt",
 "Ruby Baseball Jersey",
 "Apache Baseball Jersey",
 "Ruby on Rails Mug",
 "Ruby on Rails Stein"]

といったようにProductモデル内のnameカラムを一覧表示することができます。

Product.pluck(:id,:name)
=> [[1, "Ruby on Rails Tote"],
 [2, "Ruby on Rails Bag"],
 [3, "Ruby on Rails Baseball Jersey"],
 [4, "Ruby on Rails Jr. Spaghetti"],
 [5, "Ruby on Rails Ringer T-Shirt"],
 [6, "Ruby Baseball Jersey"],
 [7, "Apache Baseball Jersey"],
 [8, "Ruby on Rails Mug"],
 [9, "Ruby on Rails Stein"]]

このように第二引数を設定することも可能です。

pluckの処理内容と、第二引数を設定した場合の処理がどのように行われているか気になったので、ソースコードを見てみることにしました。

Railsのソースコード

rails/activesupport/lib/active_support/core_ext/enumerable.rb
def pluck(*keys)  
  if keys.many?  
    map { |element| keys.map { |key| element[key] } }  
  else  
  key = keys.first  
    map { |element| element[key] }  
  end  
end

実際に読んでみる①

まず、引数が一つのケースで考えてみます。

[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)  
# => ["David", "Rafael", "Aaron"]
def pluck(*keys)  
  if keys.many?  
    map { |element| keys.map { |key| element[key] } }  
  else  
  key = keys.first  
    map { |element| element[key] }  
  end  
end

一行目
(*keys)に、:nameが配列として格納されます。

pluck(*keys): *をつければ引数を複数個設定できる。(可変長引数)  引数は配列として受け取られる。

二行目
keys.many? → 今回、keysは一つだけなのでスルー

many? → 条件を満たす要素が 2 つ以上ある場合に true。

五行目
keysの配列で、最初の要素(:name)を、keyに格納

六行目

[{ name:  "David"  },  { name:  "Rafael"  },  { name:  "Aaron"  }]

上記の配列をmapメソッドでそれぞれ、key に関連づけられた値を抽出。その後、配列として出力した結果、

=> ["David", "Rafael", "Aaron"]

nameカラムの要素のみ表示できました。

実際に読んでみる②

次に、引数が2つ以上のケースで考えてみます。

[{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name)  
# => [[1, "David"], [2, "Rafael"]]
def pluck(*keys)  
  if keys.many?  
    map { |element| keys.map { |key| element[key] } }  
  else  
  key = keys.first  
    map { |element| element[key] }  
  end  
end

一行目
(*keys)に、:id, :name が配列として格納されます。

二行目
keysが、2つ以上なのでそのまま進みます。

三行目

[{ id: 1, name: "David" }, { id: 2, name: "Rafael" }]

まず、上記の配列からmapメソッドで、keys → (:id, :name) に関連づけられた要素を抽出。
そこからさらに、mapメソッドを使って配列の要素それぞれに、上記の処理を行う。

その後、配列として出力した結果、

=> [[1, "David"], [2, "Rafael"]]

idカラムとnameカラムの要素を表示することができました。

終わりに

ソースコードのどこを読めばいいのかとか、おろそかにしてきた基礎文法の知識の足りなさが露呈したりと、たったこれを読むだけでもかなり苦戦しました。
ただ、今まで公式ドキュメント読んでも、「...?」だった内容が事細かに書いてある(当たり前)ので、ソースコードで確認することで、どういう仕組みか納得して進められるなと思った次第です。
これからも、誰かのソースコードを読むことは必須になってくるので、気になったメソッドとかがあったらソースコードを見てみる癖を付けておきたいなと思うのでした。

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