20201127のRailsに関する記事は17件です。

ActiveHashを使ってカテゴリーの選択を実装

ActiveHashとは「基本的に変更されないデータの取り扱い」があったとします。このデータはデータベースに保存する必要がありません。一方、ビューファイルなどにそれらのデータを直接書いてしまうと、可読性に欠けます。そのようなケースでは、ActiveHashが有用です。

ActiveHashをインストール

 gem 'active_hash'

モデル生成(今回はカテゴリー選択を"genre"とします)

rails g model genre --skip-migration

この時のモデル作成には「--skip-migration」を使用しております。
これはモデルファイルを作成するときに、マイグレーションファイルの生成を行わないためのオプションです。
選択したジャンルの情報はデータベースに保存しないため、マイグレーションファイルを作成する必要がないためです。

ActiveHash::Base

ActiveHash::Baseは、あるモデル内(クラス内)でActiveHashを用いる際に必要となるクラスです。

app/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: 'その他' }
 ]
   include ActiveHash::Associations
   has_many :articles

 end

マイグレーションファイル内

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

ターミナル

% rails db:migrate

親モデルのアソシエーションを設定

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
app/views/articles/new.html.erb
<%= form_with model: @article, url:articles_path, local: true do |f| %>
  <div class="article-box">
    記事を投稿する
    <%= f.text_field :title, class:"title", placeholder:"タイトル" %>
    <%= f.text_area :text, class:"text", placeholder:"テキスト" %>
    <%= f.collection_select(:genre_id, Genre.all, :id, :name, {}, {class:"genre-select"}) %>
    <%= f.submit "投稿する" ,class:"btn" %>
  </div>
<% end %>

以上備忘録でした。

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

deviseを使って、ユーザー情報の編集・更新について

はじめに

 昨日、deviseでユーザーマイページの詳細ページがない、ということを投稿したが、アプリの制作を進めていると、編集ページは見つけたので、記録しておく。

ユーザー情報の編集ページはdeviseにあった!

ターミナルで

rails routes

をすると、

edit_user_registration GET      (URI略)      users/registrations#edit
     user_registration PATCH    (URI略)      users/registrations#update

views/devise/registrations/edit.html.erb
ファイルに、ユーザーの編集ページとして、デフォルトであり、パスワードやメールアドレスの編集ができるようになっている。このファイルにlink_toで飛ばすと、簡単に編集・更新できる。
デフォルトであるビューで、更新機能も備わっている。
更新が成功すると、ルートパスにリダイレクトもされる。

最後に

おそらく、コントローラーに記述があるんだろうけど、どの部分か見つけられない今日この頃。

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

Rails RSpec による画像添付結合テスト

はじめに

RSpecによるオリジナルアプリの結合テストを行っています。画像投稿機能において、「テスト用の画像を添付する」、「添付したテスト用の画像が画面上にあるか」の2つを忘れていたので、備忘録として書き記します。

#目次
1.テスト用の画像を添付する
2.添付したテスト用の画像が画面上にあるかテストする

1.テスト用の画像を添付する

ここではimage_pathという変数にテスト用画像(test_image.jpg)を添付する。この時相対パスで指定する。
attach_fileメソッドはアップロードのinput要素にテスト用画像を添付することができる。第一引数にアップロードするinput要素のname属性の値、第二引数にアップロードする画像のパス、第三引数にオプション(ここではmake_visible: trueで一時的に表示)を設定する。

spec/system/◯◯_spec.rb
# 添付する画像を定義する
image_path = Rails.root.join('public/images/test_image.jpg')

# 画像選択フォームに画像を添付する
attach_file('desk[image]', image_path, make_visible: true)

2.添付したテスト用の画像が画面上にあるかテストする

画像を投稿すると、画像一覧ページに遷移する。遷移先で投稿した画像があるかの確認は下記コードに記す。have_selectorで要素があるか判断する。ちなみにhave_content()で文字列があるかのマッチャになる。

spec/system/◯◯_spec.rb
expect(page).to have_selector("img[src$='test_image.jpg']")

以上

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

【Rails】Ajaxでいいねの数を表示する方法

概要

前回の続きでいいねカウントの実装をしていいねの数を表示していきたいと思います。

APIの作成

まずJavaScriptからリクエストが来たときに返したいデータを定義していきます。
JavaScriptでリクエストを送ったら現在のいいね数をresponseで返すことができれば、実装できると思いました。

いいねの数を取得するために、LikesControllerのshowアクション内でlike_countを定義しました。

app/controllers/likes_controller.rb
def show
  like_count = @team.likes.count
  render json: { likeCount: like_count }
end

ただ、私が実装したいことは

現在のいいね数をDOMContentLoadedイベント時に取得することだけでなく、POSTとDELETEのリクエストの時にも取得したいと考えています。
そのため、showアクション内にだけ定義するのではなくcreate, destroyにも定義していく必要があります。createdestroyでもrender jsonlikeCountを入れないとJavaScriptからのリクエストに対応するデータがないのでundefinedになって返ってきてしまいます。

前回と重複するところもありますが、LikesControllerの記述はこのようになると思いました。before_action :like_countsでアクションの最初に読み込むようにしています。

LikesController

app/controllers/likes_controller.rb
class LikesController < ApplicationController
  before_action :authenticate_user!
  before_action :set_like
  before_action :like_counts

  def show
    like_status = current_user.has_liked?(@team)

    render json: { hasLiked: like_status, likeCount: like_counts }.to_json
  end

  def create
    @team.likes.create!(user_id: current_user.id)

    render json: { status: 'ok', likeCount: like_counts }.to_json
  end

  def destroy
    like = @team.likes.find_by!(user_id: current_user.id)
    like.destroy!

    render json: { status: 'ok', likeCount: like_counts }.to_json
  end

  private

  def set_like
    @team = Team.find(params[:team_id])
  end

  def like_counts
    @like_count = @team.likes.count
  end
end

どこにいいねの数を表示するのか

show.html.haml
.star_counter
  現在
  %span
  がいいねをしています。

今回、star_counterクラス内のspanタグ内にいいねの数を入れていこうかと思います。spanを入れた理由はいいねの数をscssでスタイルを調整しようと考えているからです。たとえば、以下のようにすればいいねの数だけ文字が赤く太くなります。

SCSS

scss
.star_counter {
  span {
    color: red; // 好きな色
    font-weight: bold; // 線を太くして強調
  }
}

いいねの数を取得(GET)

JavaScriptで実装したいこととしてはまず、GETリクエストで現在のいいねの数を取得することです。
そのため、GETリクエストが成功したらいいねの数を表示させていきます。

コードにするとこのようになると思いました。 ※前回と重複するところは省略しております。

jQuery
// GET, POST, DELETEリクエストで使うので処理をまとめています。
const likeCountCalculation = (likeCount) => {
// いいねの数を表示させたいところを取得。今回はstar_counterクラス内のspan要素を取得しています。その後appendで追加していくという処理です。
  $('.star_counter > span').append(
    `${likeCount}`
  )
}

// axiosでGETリクエスト成功
.then((response) => {
// response内に先程Controllerで書いたlikeCountがjson形式で返されるので取得。
  const likeCount = response.data.likeCount
// 上で定義してあるlikeCountCalculation()が呼び出されて、span要素内に現在のいいねの数が表示されるようになります。現在のいいねの数はlikeCountに入っているので関数を呼び出すときにを引数にいれています。
  likeCountCalculation(likeCount)
})

いいねの数の増減 (POST, DELETE)

はじめは、likeのcountを+1, -1することで実装すればと思っていたのですが、それよりもGETの処理と同じ考え方で現在のいいねの数を取得すれば良いのでは?と考えました。現在のというのは+1や-1されたあとのいいねの数のことです。要するに、POSTやDELETEリクエストが成功すればいいねの数は増減しているのでその値を取得することで実装できると思いました。
コードにするとこのようになると思います。POSTもDELETEも同じ記述なのでまとめて書かせていただきます。GETの処理と違う点は、リクエストが成功したらspan要素の中身を空にするということです。これをしないとリクエストの度に要素が追加されていってしまいます。

jQuery
// axiosでリクエスト(POST or DELETE)成功
.then((response) => {
  // responseに入ってくるいいねの数を取得
  const likeCount = response.data.likeCount
  // star_counterクラス内のspan要素の中身を空にする。
  $('.star_counter > span').html('')
  // 現在いいねの数を表示する。
  likeCountCalculation(likeCount)
})

まとめ

  • 前回同様やりかたは一つではないこと。
  • 親要素内の子要素を取得する方法もひとつではなく選択肢がたくさんあり、わかりやすいものを使う。(読みやすいコードだったり状況に応じて判断)
  • jsonで複数の値を返すには記述を変える必要がある。

最後に

自分の実装したいことをどうやって実現していくのか、ここの言語化が大事だと感じました。実装したいことに対して、いくつもの方法があるということは、簡単に書けるものだったり逆に複雑になってしまったりすることがあると思います。こういったところもたくさんコードを書いたり技術書を読んだり、記事を見たりして学習していくことで力をつけていきます。

参考文献

前回書いた記事です。いいねの実装をこちらでしているのでよろしければご覧ください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsでdeviseとscaffoldを組み合わせた際に調べたことをまとめてみた。

はじめに

Ruby on Railsでdeviseとscaffoldを使って簡易アプリを作っているときに分からないコードなど、調べたことを自分用にまとめてみました。

環境


MacOS Catalina version 10.15.7
Rails 6.0.3.4
devise 4.7.3

前提


$ rails new アプリ名でアプリができている状態

deviseの導入


以下のurlのページを参考にdeviseでログイン機能等を作成する。
rails g devise:installでファイルの作成を終えた後、下のコードを記述する。
config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

こちらのページ⇨ https://qiita.com/Orangina1050/items/a16e655519a60f35b394

scaffoldの導入

既に作ってあるアプリにディレクトリを移動した後、以下の形式でscaffoldを実行する。

$ rails g scaffold モデル名 カラム1:データ型1 カラム2:データ型2

自分は自己紹介をする簡易アプリなので以下のカラムを持たせた。

$ rails g scaffold intro name:string hometown:string text:content

scaffold実行後以下を行う。

$ rails db:migrate

カラムをつけ忘れて追加したい場合

rails g migration 行なう処理Toテーブル名 カラム名:データ型の形式で実行する。カラムを加えるなら、AddColumnToテーブル名というように書く。

自分は、user_idのカラムをつけ忘れたため、

$ rails g migration AddColumnToIntros user_id:integer

を行った。rails db:migrateも忘れずに。

コントローラーに設定を加える

scaffoldによってできたコントローラー(intros_controller.rb)の一番上に、
before_action :authenticate_user!を記入する。

before_action :authenticate_user!とは

authenticate_user!はdeviseを入れる事で使えるようになるヘルパーの一つで、
コントローラーに設定してログイン済ユーザーのみにアクセスを許可する。
参考 https://qiita.com/ryuuuuuuuuuu/items/bf7e2ea18ef29254b3dd

次に、newアクション内に下記を追加する。

app/controllers/intros_controller.rb
@intro = Intro.find_or_create_by(:user_id => current_user.id)

find_or_create_byとは

は引数の条件に該当するデータがあればそれを返しfind_by(attributes)、なければ新規作成create(attributes, &block)します。今回は、user_idがcurrent_user.idの場合、それを取得、なければ作成を行っている。
参考 https://qiita.com/taimuzu/items/0a21738d018f475d63ae

自己紹介を既に作成しているユーザーが新規作成のボタンを押したときに編集ページにとんでほしいのでnewアクションに

app/controllers/intros_controller.rb
redirect_to edit_intro_url(@intro)

を追記する。これはredirect_to edit_intro_url(@user.id)と同じことをやっている。
参考 https://qiita.com/Kawanji01/items/96fff507ed2f75403ecb

redirect_toのパス確認

さっき指定したリダイレクト先は、以下のコマンドを打つと様々なパスが出てくるので、そこから対象のものを探す。edit_intro_urlの部分。

$ rails routes

最後に、editアクションにidがcurrent_user.idかそうでないか分岐する処理を書く。

app/controllers/intros_controller.rb
 if @intro.user_id != current_user.id 
    flash[:notice] = "他のユーザーの編集はできません。"
    redirect_to intros_path
 end

その際に、もとからindex.html.erbとshow.html.erbに書かれている、<p id="notice"><%= notice %></p>をコメントアウトすることで、flash[:notice]の内容を表示することができる。

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

Ruby on Railsでdeviseとscaffoldを組み合わせた際に調べたところをまとめてみた。

はじめに

Ruby on Railsでdeviseとscaffoldを使って簡易アプリを作っているときに分からないコードなど、調べたことを自分用にまとめてみました。

環境


MacOS Catalina version 10.15.7
Rails 6.0.3.4
devise 4.7.3

前提


$ rails new アプリ名でアプリができている状態

deviseの導入


以下のurlのページを参考にdeviseでログイン機能等を作成する。
rails g devise:installでファイルの作成を終えた後、下のコードを記述する。
config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

こちらのページ⇨ https://qiita.com/Orangina1050/items/a16e655519a60f35b394

scaffoldの導入

既に作ってあるアプリにディレクトリを移動した後、以下の形式でscaffoldを実行する。

$ rails g scaffold モデル名 カラム1:データ型1 カラム2:データ型2

自分は自己紹介をする簡易アプリなので以下のカラムを持たせた。

$ rails g scaffold intro name:string hometown:string text:content

scaffold実行後以下を行う。

$ rails db:migrate

カラムをつけ忘れて追加したい場合

rails g migration 行なう処理Toテーブル名 カラム名:データ型の形式で実行する。カラムを加えるなら、AddColumnToテーブル名というように書く。

自分は、user_idのカラムをつけ忘れたため、

$ rails g migration AddColumnToIntros user_id:integer

を行った。rails db:migrateも忘れずに。

コントローラーに設定を加える

scaffoldによってできたコントローラー(intros_controller.rb)の一番上に、
before_action :authenticate_user!を記入する。

before_action :authenticate_user!とは

authenticate_user!はdeviseを入れる事で使えるようになるヘルパーの一つで、
コントローラーに設定してログイン済ユーザーのみにアクセスを許可する。
参考 https://qiita.com/ryuuuuuuuuuu/items/bf7e2ea18ef29254b3dd

次に、newアクション内に下記を追加する。

app/controllers/intros_controller.rb
@intro = Intro.find_or_create_by(:user_id => current_user.id)

find_or_create_byとは

は引数の条件に該当するデータがあればそれを返しfind_by(attributes)、なければ新規作成create(attributes, &block)します。今回は、user_idがcurrent_user.idの場合、それを取得、なければ作成を行っている。
参考 https://qiita.com/taimuzu/items/0a21738d018f475d63ae

自己紹介を既に作成しているユーザーが新規作成のボタンを押したときに編集ページにとんでほしいのでnewアクションに

app/controllers/intros_controller.rb
redirect_to edit_intro_url(@intro)

を追記する。これはredirect_to edit_intro_url(@user.id)と同じことをやっている。
参考 https://qiita.com/Kawanji01/items/96fff507ed2f75403ecb

redirect_toのパス確認

さっき指定したリダイレクト先は、以下のコマンドを打つと様々なパスが出てくるので、そこから対象のものを探す。edit_intro_urlの部分。

$ rails routes

最後に、editアクションにidがcurrent_user.idかそうでないか分岐する処理を書く。

app/controllers/intros_controller.rb
 if @intro.user_id != current_user.id 
    flash[:notice] = "他のユーザーの編集はできません。"
    redirect_to intros_path
 end

その際に、もとからindex.html.erbとshow.html.erbに書かれている、<p id="notice"><%= notice %></p>をコメントアウトすることで、flash[:notice]の内容を表示することができる。

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

GitHub Actionsを使ってRailsアプリをデプロイしてみた

GitHub Actions初めて使ってみました。設定楽でいいですね。

使い方(ざっくり)

  • プロジェクトルートに.github/workflowディレクトリを作成
  • その中にymlファイルを作って、実行したい処理を書く
  • 環境変数を使いたい場合はGitHubにアクセスしてSettings→Secretsで設定する
    • ymlファイル内で${{ secrets.HOGEHOGE }}でアクセスできる

使い方(実際やったこと)

masterブランチが更新された時にRailsアプリをEC2にデプロイする設定をしました。
まずGitHub上で3つの環境変数(PRIVATE_KEYUSER_NAMEHOST_NAME)を登録。
その後アプリに戻り以下を実行。

$ midir -p ./github/workflow
$ touch ./github/workflow/rails.yml
/github/workflow/rails.yml
name: Rails CI/CD
## ↓実行タイミング
on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Deploy
      # ↓環境変数
      env:
        PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
        USER_NAME: ${{ secrets.USER_NAME }}
        HOST_NAME: ${{ secrets.HOST_NAME }}
      # ↓実行したいコマンド
      run: |
        echo "$PRIVATE_KEY" > private_key && chmod 600 private_key
        ssh -o StrictHostKeyChecking=no -i private_key ${USER_NAME}@${HOST_NAME} 'cd sample_app &&
        git pull origin master &&
        ~/.rbenv/shims/bundle install &&
        ~/.rbenv/shims/bundle exec rails assets:precompile RAILS_ENV=production &&
        ~/.rbenv/shims/bundle exec rails db:migrate RAILS_ENV=production &&
        kill $(cat tmp/pids/puma.pid) &&
        ~/.rbenv/shims/rails s -e production'

masterブランチが更新された時、GitHubのActionsタブにアクセスすると、作成したワークフローが正常に動いているか確認することができます。

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

Rails seedファイルによる初期データの設定

HerokuでRailsアプリをデプロイした際に自身のアプリケーションでは管理権限のあるユーザーしかユーザーを作成出来ない仕様のアプリケーションなのですが、root画面がログイン画面なので操作出来ないと思い色々考えていたらseedファイルという便利なものがあるのを知り導入しました。

seedファイルとは

初期データを記述できるファイルです。これがある事で上の件であったり、データベースを作り直す度にテストユーザーの作成時間を短縮する事が出来ます。

db/seeds.rbに記述します。

User.create!(
   name: 'テスト',
   email: 'test@test.com',
   password: 'xxxxxx',
   admin: true
)

開発環境に反映する

$rails db:seed

Heroku: 本番環境に反映する

heroku run rake db:seed

↑がないと本番環境に反映されません。

ちなみになんですが恥ずかしながら、HerokuでもRails cが使えるのを知らなかったです。

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

[Rails]deleteメソッドとdestroyメソッドの違いについて!![初心者]

はじめに

先日、とあるアプリケーションを作成していて、deleteメソッドdestroyメソッドのどちらを使うべきか、分からなくなってしまったので、備忘録も兼ねてアウトプットします!!

deleteメソッドについて

指定した条件のレコードを、SQLを直接実行することで削除します。
ActiveRecordを介さないということなので、モデルを経由しません。
また、モデルで関連付けされているレコードがあったとしても、そちらは削除されません。

User.find(id: 1).delete

destroyメソッドについて

ActiveRecordを介して、指定した条件のレコードを削除するメソッドです。
モデル内で、dependent: :destoryが設定されていれば、関連付けされているデータも併せて削除されます。
ActiveRecordを介しているので、callbackメソッドやバリデーションを機能させることもできます。

メソッドの実行時にエラーが発生し、削除ができなかった場合は、falseを返すだけで、例外は返却されません。
なので、「とりあえずデータを削除したいが、失敗した場合はエラーを返却してほしいな...」という場合は、destroy!メソッドを使用すれば大丈夫です。

User.find(id: 1).destroy

例えば、Userモデルに多数のBookモデルが関連付けされている場合、id: 1のユーザーが投稿したbookに関しても、同時に削除がされることになります。

ActiveRecordって何なん??

ActiveRecordとは、RubyとSQLの翻訳機のようなイメージです。

本来、DBで使用されるDB言語には、SQLが使用されています。
しかし、Railsが開発してくれているActiveRecordのおかげで、我々はRubyを使用して、DBからデータを探したり、持って来れたり出来るというわけなのです!!
非常に便利な機能ですね。

delete_allとdestroy_allについて

まとめて紹介しますが、こっちを使用すると、条件に合致する複数のレコードを同時に削除することが可能です。
whereメソッドと同時に使われることが多いです!!

Book.where(user_id: 1).delete_all
Book.where(user_id: 1).destroy_all

おわりに

Railsには、似通っている機能が多数存在するので、混乱することも多いですが、一つ一つ丁寧に理解していくことが、上達への近道になりそうですね。

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

railsでマイグレーションファイルを編集する際に行う事(rails db:rollback)

マイグレーションファイルを編集したい

マイグレーションファイルの記述を間違えてしまった、コードを付け加えたいと思った時は、行う手順があります。

まずはマイグレーションファイルの状態を確認します。
% rails db:migrate:status

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20201119073257  Devise create users
   up     20201120031505  Create coordinations
   up     20201120032421  Create active storage tablesactive storage
今回はusersのマイグレーションファイルを編集を行いますので次のコマンドを実行します。
rails db:rollback
再度statusコマンドでマイグレーションファイルの状態を確認します。
% rails db:migrate:status

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20201119073257  Devise create users
   up     20201120031505  Create coordinations
  down    20201120032421  Create active storage tablesactive storage
これではusersのマイグレーションファイルがdownになっていないので編集出来ません。

ロールバックコマンドを行うと直近のマイグレーションファイルのみがdownになり編集出来る状態になります。
今回のusersマイグレーションファイルのように、最後から3つ目のマイグレーションファイルを編集、変更したい場合は

rails db:rollback STEP=3 

とSTEP=○を追記すると一度でusersマイグレーションファイルまでdownにすることが出来ます。

rails db:migrate:status

 Status   Migration ID    Migration Name
--------------------------------------------------
  down    20201119073257  Devise create users
  down    20201120031505  Create coordinations
  down    20201120032421  Create active storage tablesactive storage

STEP=○ ←○の中には最後に行ったマイグレーションファイルからrollbackを行いたいマイグレーションファイルまでいくつのマイグレーションファイルが含まれているか数えてその数字を入れてあげればうまくいきます。
最後を含め3つ目ならrails db:rollback STEP=3
4つ目ならrails db:rollback STEP=4

最後に

これでマイグレーションファイルの変更を行うことが出来ます。

変更が完了したらマイグレーションを実行をお忘れずに!

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

ActiveSupport::Callbacksを読む

  • 明日の朝までに読まないといけない

背景

  • とあるコントローラの処理が重い
  • 調査の結果、before_actionとかafter_actionとかで指定されているfilterに原因があることが分かった
  • そこで、それぞれのCallbackの実行にかかる時間を書き出したい
  • そのために、該当するControllerのrun_callbacksメソッドを黒魔術で上書きする

ActiveSupport::Callbacksについて

  • ApplicationControllerやActiveRecordがこれをincludeして、run_callbacksなどのメソッドを使用可能になる。

今回読むコード

https://github.com/rails/rails/blob/fbe2433be6e052a1acac63c7faf287c52ed3c5ba/activesupport/lib/active_support/callbacks.rb#L97-L142

activesupport/lib/active_support/callbacks.rb
    # Runs the callbacks for the given event.
    #
    # Calls the before and around callbacks in the order they were set, yields
    # the block (if given one), and then runs the after callbacks in reverse
    # order.
    #
    # If the callback chain was halted, returns +false+. Otherwise returns the
    # result of the block, +nil+ if no callbacks have been set, or +true+
    # if callbacks have been set but no block is given.
    #
    #   run_callbacks :save do
    #     save
    #   end
    #
    #--
    #
    # As this method is used in many places, and often wraps large portions of
    # user code, it has an additional design goal of minimizing its impact on
    # the visible call stack. An exception from inside a :before or :after
    # callback can be as noisy as it likes -- but when control has passed
    # smoothly through and into the supplied block, we want as little evidence
    # as possible that we were here.
    def run_callbacks(kind)
      callbacks = __callbacks[kind.to_sym]
      if callbacks.empty?
        yield if block_given?
      else
        env = Filters::Environment.new(self, false, nil)
        next_sequence = callbacks.compile
        invoke_sequence = Proc.new do
          skipped = nil
          while true
            current = next_sequence
            current.invoke_before(env)
            if current.final?
              env.value = !env.halted && (!block_given? || yield)
            elsif current.skip?(env)
              (skipped ||= []) << current
              next_sequence = next_sequence.nested
              next
            else
              next_sequence = next_sequence.nested
              begin
                target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
                target.send(method, *arguments, &block)
              ensure
                next_sequence = current
              end
            end
            current.invoke_after(env)
            skipped.pop.invoke_after(env) while skipped && skipped.first
            break env.value
          end
        end
        # Common case: no 'around' callbacks defined
        if next_sequence.final?
          next_sequence.invoke_before(env)
          env.value = !env.halted && (!block_given? || yield)
          next_sequence.invoke_after(env)
          env.value
        else
          invoke_sequence.call
        end
      end
    end

binding.pryで実験

[9] pry(#<Api::V2::AppleInAppPurchaseController>)> callbacks.class
=> ActiveSupport::Callbacks::CallbackChain
[10] pry(#<Api::V2::AppleInAppPurchaseController>)> callbacks.compile.class
=> ActiveSupport::Callbacks::CallbackSequence

callbacks

=> #<ActiveSupport::Callbacks::CallbackChain:0x00007f907ffdd998
 @callbacks=
  #<ActiveSupport::Callbacks::CallbackSequence:0x00007f9064416350
   @after=[],
   @before=[],
   @call_template=
    #<ActiveSupport::Callbacks::CallTemplate:0x00007f9064416378
     @arguments=[],
     @method_name=:set_controller_action,
     @override_block=nil,
     @override_target=nil>,
   @nested=
    #<ActiveSupport::Callbacks::CallbackSequence:0x00007f9064417ca0
     @after=
      [#<Proc:0x00007f90644175e8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:232>,
       #<Proc:0x00007f9064416c60@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:247>,
       #<Proc:0x00007f9064416a08@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:247>],
     @before=
      [#<Proc:0x00007f9064416468@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
       #<Proc:0x00007f90644165f8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
       #<Proc:0x00007f9064416850@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
       #<Proc:0x00007f9064416e68@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:175>,
       #<Proc:0x00007f9064417188@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
       #<Proc:0x00007f9064417368@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
       #<Proc:0x00007f90644178e0@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>],
     @call_template=nil,
     @nested=nil,
     @user_conditions=nil>,
   @user_conditions=[]>,
 @chain=
  [#<ActiveSupport::Callbacks::Callback:0x00007f90643c1738
    @chain_config=
     {:scope=>[:kind],
      :terminator=>
       #<Proc:0x00007f9072ce44d8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/actionpack-6.0.3.2/lib/abstract_controller/callbacks.rb:34 (lambda)>,
      :skip_after_callbacks_if_terminated=>true},
    @filter=:set_controller_action,
    @if=[],
    @key=:set_controller_action,
    @kind=:around,
    @name=:process_action,
    @unless=[]>,
   #<ActiveSupport::Callbacks::Callback:0x00007f90643add28
    @chain_config=
     {:scope=>[:kind],
      :terminator=>
       #<Proc:0x00007f9072ce44d8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/actionpack-6.0.3.2/lib/abstract_controller/callbacks.rb:34 (lambda)>,
      :skip_after_callbacks_if_terminated=>true},
    @filter=:init_i18n_debugger,
    @if=[],
    @key=:init_i18n_debugger,
    @kind=:before,
    @name=:process_action,
    @unless=[]>,
   #<ActiveSupport::Callbacks::Callback:0x00007f9072cfd938
    @chain_config=
     {:scope=>[:kind],
      :terminator=>
       #<Proc:0x00007f9072ce44d8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/actionpack-6.0.3.2/lib/abstract_controller/callbacks.rb:34 (lambda)>,
      :skip_after_callbacks_if_terminated=>true},
    @filter=
     #<Proc:0x00007f9072cfdb90@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/config-2.2.1/lib/config/integrations/rails/railtie.rb:28>,

callback_chains

=> #<ActiveSupport::Callbacks::CallbackSequence:0x00007f9064416350
 @after=[],
 @before=[],
 @call_template=
  #<ActiveSupport::Callbacks::CallTemplate:0x00007f9064416378
   @arguments=[],
   @method_name=:set_controller_action,
   @override_block=nil,
   @override_target=nil>,
 @nested=
  #<ActiveSupport::Callbacks::CallbackSequence:0x00007f9064417ca0
   @after=
    [#<Proc:0x00007f90644175e8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:232>,
     #<Proc:0x00007f9064416c60@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:247>,
     #<Proc:0x00007f9064416a08@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:247>],
   @before=
    [#<Proc:0x00007f9064416468@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
     #<Proc:0x00007f90644165f8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
     #<Proc:0x00007f9064416850@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
     #<Proc:0x00007f9064416e68@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:175>,
     #<Proc:0x00007f9064417188@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
     #<Proc:0x00007f9064417368@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
     #<Proc:0x00007f90644178e0@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>],
   @call_template=nil,
   @nested=nil,
   @user_conditions=nil>,
 @user_conditions=[]>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsの基本を振り返ってみる

今更ながら、Railsの基本をもう一度振り返っておさらいしてみる

Railsとは

Rubyで作られたフレームワークのこと
「MVC」というアーキテクチャパターンを採用している

アーキテクチャパターンとは

一言でいうならば、「ソフトウェアアーキテクチャで発生する問題の解決策」
を指す。似た表現である「アーキテクチャ スタイル 」も同意と考えてよさそう

「MVC」とは

アプリケーションを以下の3つで考えること
M Model…主にシステムの処理を行う場所
V View…画面の描画を担当する場所
C Controller…ModelとView間の間にたち、データの受け渡しを行う場所

以下で更に詳しくみていきます。

Modelとは

主に、DBとのやりとりを行ってデータを取得したり格納したりします。
役割としては、ビジネスロジックを担う
例)給与計算など

Viewとは

アプリケーションの見た目を担う
Modelを介して、取得したデータを受け渡し用の変数等に格納して
動的にテンプレートで表示する
ロジック等も記述することができるが、うまくModelとControllerを活用する

Cotrollerとは

ユーザーのリクエストを受けて、モデルに適切な動作を要求する
リクエストはURLとして届くため、届いたURLを分析し適切なアクションへと
導くのが役目
さらに、Webに関する一般的な仕事を受け持つという側面がある
例)
- セッション管理URLの解釈
- HTTPリクエスト
- レスポンスの処理
- クッキーの管理などを担当

参考記事

MVC
https://kitsune.blog/rails-summary
https://www.atmarkit.co.jp/ait/articles/1102/23/news109.html

アーキテクチャパターン
https://qiita.com/cocoa-maemae/items/f276fe14b9700fdae7bc

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

【Rails】AWSデプロイ中にCould not find aws-eventstream-1.1.0 in any of the sources Run `bundle install` to install missing gems.

EC2内でDBを作成しようとしたところエラーが出て詰まったので忘備録として書きます

EC2内でデータベースを作成しようと下記のコマンドを実行したところ
ターミナル(EC2内で実行)
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ rails db:create RAILS_ENV=production
Could not find aws-eventstream-1.1.0 in any of the sources
Run `bundle install` to install missing gems.

とエラー
どうやらどのソースでもaws-eventstream-1.1.0が見つかりませんでしたbundle installを実行して、不足しているgemをインストールします。と言われているよう...
まずGemfileをのぞくが
Gemfile
gem "aws-sdk-s3", require: false

一番下に記述はしっかりあることを確認。

Gemfile.lockをのぞきにいくが
63行目に以下の記述は入っている
Gemfile.lock
aws-eventstream (1.1.0)

bundleinstallのし忘れかと思い履歴を見るがしっかりEC2内にて参考資料通り実行済み、
作業を一度ふり返るが特にミスした形跡も見当たらずもちろんググってみるがあまり参考になる記事も見つけられず。
どうやら開発環境ではaws-eventstream (1.1.0)は存在するが本番環境では適用されてないのだとあたりをつけて

念のためもう一度bundle install
ターミナル(EC2内)
[ec2-user@ip-10-0-0-25 <リポジトリ名>]$ bundle install
Using actionmailer 6.0.3.3
Using actiontext 6.0.3.3
Using public_suffix 4.0.6
Using addressable 2.7.0
Using ast 2.4.1
Fetching aws-eventstream 1.1.0
Installing aws-eventstream 1.1.0
Fetching aws-partitions 1.399.0
Installing aws-partitions 1.399.0
Fetching aws-sigv4 1.2.2
Installing aws-sigv4 1.2.2
Fetching jmespath 1.4.0
Installing jmespath 1.4.0
Fetching aws-sdk-core 3.109.3
Installing aws-sdk-core 3.109.3
Fetching aws-sdk-kms 1.39.0
Installing aws-sdk-kms 1.39.0
Fetching aws-sdk-s3 1.85.0
Installing aws-sdk-s3 1.85.0

と、どうやらinstall されていなかったようです。
再度
ターミナル(EC2内)
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ rails db:create RAILS_ENV=production
Created database '<データベース名>'

と成功!!
今回原因はbundleinstallし忘れだったようだが、他のgemはしっかり反映されてたのでそれが原因突き止められなかったがしっかり一つ一つ検証していき可能性を消していく作業が大事だと感じた。
同じような症状で悩まれるかたに是非参考になればと思い書かせていただきました。最後まで読んでいただきありがとうございました。

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

Railsのrouting設定について

本投稿の目的

・Rails学習の議事録です。


学習に使った教材

Udemyの以下2つの教材を参考にまとめました。
"はじめてのRuby on Rails入門-RubyとRailsを基礎から学びWebアプリケーションをネットに公開しよう"
"フルスタックエンジニアが教える 即戦力Railsエンジニア養成講座"


○RESTfull

・HTTPメソッドにそってWebのリソースを設計する設計思想
・RailsのRouting設定はこの思想を表現したようなもの

【HTTPメソッド】
・GET  リソースの取得
・POST リソースの作成
・PATCH/PUT リソースの更新
・DELTE リソースの削除

○Routingとは?

以下の①~④を指定する設定

①どのHTTPメソッドで
②どのurlへアクセスした際に
③どのcontrollerの
④どのアクションを実行するか

○Routing設定方法

・/config/routes.rb を開く
・このファイルを編集することでrouting変更が可能
*(rails g controller アクション名で記述したroutingが自動記述済み)

【rootの設定】

・urlに"/"でアクセスした際のrouting設定
・以下を記述 (*アクション名は"#"で記載することに注意)

qiita.rb
root 'controller名#アクション名''

【例:rootでquestions controllerのindexアクションを実行したい場合】

qiita.rb
root 'questions#index'

【書き方①】

・HTTPメソッドでPrefix_pathのurlへアクセスするとtoo以降のcontrollerでアクションを実行
・という設定を意味する
・以下を記述 (Prefixの部分は _pathを除くことに注意)

qiita.rb
HTTPメソッド 'Prefix(_pathは除く)', too: 'controller名#アクション名'

【例:boards controllerのnewアクションを想定】

qiita.rb
get 'boards/new', too: 'boards#new'

【書き方②】

【基本的な記述】
・Railsでdefaultで設定された分のroutingが自動で設定可能
・以下を記述

qiita.rb
resources :controller

【応用的な記述】
・指定したアクション名のみをroutingで設定可能
・以下を記述

qiita.rb
resources :controller, only: [:アクション名1, :アクション名2]

【例:questions controller を想定】
・questions controller のindex,create,new,showに関連するroutingのみ自動設定

qiita.rb
resources :questions, only:[:index :create :new :show]

○Routing確認方法

【確認方法①(ターミナル)】
・ターミナルに以下を記述

rails routes

*【出力結果】
・今回は 【例:questions controller を想定】 の条件下での場合の出力結果

      Prefix  Verb   URI Pattern                      Controller#Action                                                                                      
    questions GET    /questions(.:format)             questions#index
              POST   /questions(.:format)             questions#create
 new_question GET    /questions/new(.:format)         questions#new
     question GET    /questions/:id(.:format)         questions#show

【解説】
・Prefix_pathで定義されたurlへアクセスした際に,それぞれのcontrollerのアクションを実行する
・これらは,routes.rbで設定したため作成された設定である

【確認方法②(Webブラウザ)】
・/rails/info/routes のpathにアクセス
・設定済みのルート情報を確認

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

【Rspec Capybara】confirmダイアログが2回表示されるsystem specを書く

やり方


単純にダイアログの回数分、

accept_confirm をネストすればOK。


# 1回目のダイアログ
page.accept_confirm do

  # 2回目のダイアログ
  page.accept_confirm do

    # ダイアログが全てOKなら実施したい何らかの処理
    first('div[data-cell="sample"]>div>form>button').click
  end
end






  • accept_confirmリファレンス

[https://www.rubydoc.info/github/jnicklas/capybara/Capybara%2FSession:accept_confirm:embed:cite]






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

Sorceryで複数モデルを扱う

Sorcery

https://github.com/Sorcery/sorcery

How

例: AdminモデルとUserモデルが存在して、両方でSorceryを使いたい場合
前提として、AdminかUserのどちらかでSorceryの導入が完了している

users_controller
def current_user
  User.find_by(id: session[:user_id])
end
config/initializer/sorcery.rb
config.user_class = 'Admin'

こうすると指定したcontroller内だけでcurrent_userの中身がuserになり、それ以外ではadminになる
上記のuserとadminを逆にしてもいいので、あまり使わない方をcontrollerに指定するとよさそう

current_userがセットされているので、before_action :require_loginも普通に動く

Single Table Inheritanceを使うやり方もあるらしいがこっちがシンプル

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

RuntimeErrorで画像投稿ができなかった場合の対処法

背景

Refileなどの画像投稿が出来るgemを導入し、画像を実際に格納してみたら
RuntimeErrorとされた。よくあることらしいので備忘録も含めて。

使用環境
ruby 2.7.2 Rails 6.0.3.4

対処法

エラー文をよく読むとそこまで難しくない。そのままの通り実行することする。

RuntimeError at / Refile.secret_key was not set.

Please add the following to your Refile configuration and restart your application:

Refile.secret_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

このRefile.secret_key='XXX'をそのままコピー。
時間を置くとkeyが変わるので注意。

/config/initializers/application_controller_renderer.rb
# Be sure to restart your server when you modify this file.

# ActiveSupport::Reloader.to_prepare do
#   ApplicationController.renderer.defaults.merge!(
#     http_host: 'example.org',
#     https: false
#   )
# end

#以下を貼り付ける
Refile.secret_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

Railsを再起動し、リロードし直すと無事画像が投稿されたのが確認された。

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