20210220のRailsに関する記事は27件です。

【Rails】時刻のJST(日本標準時)への変更

Railsアプリは、デフォルトでUTC(協定世界時)タイムゾーンを使用します。日時を扱う際、自動的にActiveSupport::TimeWithZoneクラスを用います。

タイムゾーンの変更

アプリケーションの起動時に任意のタイムゾーンの値にするには、Time.zoneの値の制御が必要です。
製作中のアプリは日本での使用を想定しているため、日本時間に変更します。config/application.rbにconfig.time_zoneから始まる1行を追加します。

config/application.rb
module TweetApp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2
    config.time_zone = 'Asia/Tokyo'
    ...
  end
end

5E583349-31E1-4EE2-929D-6BC0D0788E98.png

これで、日時がJST(日本標準時)で表示されるようになりました!

ですが、このままだと読みづらいので表示を変更します。時刻のフォーマットファイルを作成し…

config/initializers/time_formats.rb
Time::DATE_FORMATS[:datetime_jp] = '%Y/%m/%d %H:%M'

あとは以下のコードを投稿時間と更新時間の箇所へと書き換えればOK!

.created_at.to_s(:datetime_jp)
.updated_at.to_s(:datetime_jp)

参考

現場で使える Ruby on Rails 5速習実践ガイド
【Rails】created_at、updated_atを日本時間に変更して良い感じに表示する方法

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

Railsで開発中にデータベースのレコードを削除したい時は?

目的

開発中に作成したデータベース内のレコードをすべて削除したい。
例)ユーザーテーブルの「test_user」など適当に作ったユーザーデータ

方法

ターミナルで「rails db:reset」コマンドを実行。
これでデータベースの中身が空っぽになる。

terashimatakaya@MacBook-Air furima-34501 % rails db:reset
Dropped database 'furima_34501_development'
Dropped database 'furima_34501_test'
Created database 'furima_34501_development'
Created database 'furima_34501_test'

Railsガイドv6.1 「4.3データベースをリセットする」によるとこのコマンドで行ったことは、「rails db:drop db:setup」と同等とのこと。
注意点としては「rails db:migrate」の意味は含まれていない点。

解説

「rails db:drop」コマンド

データベースを削除するコマンド。usersテーブルやらが全て削除される。
「rails db:create」する前の状態に戻ると思えばOK。

「rails db:setup」コマンド

データベースの作成、スキーマの読み込み、シードデータを用いてデータベースの初期化を実行。
実際にRailsがやってる処理の流れは、
①既存のdb/schema.rbを読み込む
②①の情報を頼りにデータベースを再作成
③再作成のタイミングでdb/seeds.rbに開発用のサンプルデータがあればそれもこのタイミングで再度流し込む

補足

なぜ「db/schema.rb」を参照するのか?

簡単に説明すると、古いマイグレーションファイルたちもイチから順に実行するとエラーが起きやすくなるから。
それよりも常に最新の状態に保たれているだろうschema.rbの情報をもとにしたほうがエラーが起こらず再度データベースを作成できるから、といったかんじだろうか。
詳細は以下のリンクで確認してください。

Railsガイドv6.1 「6.1 スキーマファイルの意味について」

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

【Rails】カテゴリー機能

はじめに

レシピ投稿サイトを作成し、レシピに紐づくカテゴリー機能を実装します。
gemのancestryは使っていません。
RecipeモデルとUserモデルが紐づいており、1ユーザーが複数投稿できる仕様です。その辺りは割愛させて頂きます。
投稿(Recipe)とカテゴリー(Category)のモデル、コントローラーは実装済みで進めさせて頂き、今回は投稿とカテゴリーの紐付けについての内容に重点を置いてQiita投稿させて頂きます。

完成イメージ

入力フォーム
スクリーンショット 2021-02-20 20.38.00 (1).png
レシピ一覧
スクリーンショット 2021-02-20 20.38.51.png

中間テーブルの作成

recipe_category_relationsテーブルを中間テーブルとします。

$ rails g model Recipe_category_relation recipe_id:integer category_id:integer
db/schema.rb
 create_table "categories", force: :cascade do |t|
    t.string "category_name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "recipe_category_relations", force: :cascade do |t|
    t.integer "recipe_id"
    t.integer "category_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "recipes", force: :cascade do |t|
    t.string "recipe_title"
    t.text "recipe_body"
    t.string "image_id"
    t.integer "user_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "recipe_food"
  end

投稿のモデル、中間テーブル、カテゴリーテーブルの関連付け

app/models/recipe.rb
class Recipe < ApplicationRecord
  has_many :recipe_category_relations
  has_many :categories, through: :recipe_category_relations
end
app/models/recipe_category_relation.rb
class Recipe_category_relation < ApplicationRecord
  belongs_to :recipe
  belongs_to :category
end
app/models/category.rb
class Category < ApplicationRecord
  has_many :recipe_category_relations
  has_many :recipes, through: :recipe_category_relations
end

through: :recipe_category_relationsの記述で、中間テーブルrecipe_category_relationを経由してrecipeとcategoryが関連付けることができます。

viewに表示

app/views/recipes/new.html.erb
<h1>レシピ投稿</h1><br>
 <%= form_with model:@recipe, url: recipes_path , local:true do |f| %>
   <%= f.attachment_field :image %>
   <%= f.label :title, '料理名'%>
   <%= f.text_field :recipe_title, class:"form-control", placeholder:"料理名" %>
   <%= f.label :category, 'カテゴリ' %>
   <%= f.collection_check_boxes(:category_ids, Category.all, :id, :category_name) do |category| %>
     <%= category.label do %>
       <%= category.check_box %>
       <%= category.text %>
     <% end %>
   <% end %>
   <%= f.label :body, '材料'%>
   <%= f.text_area :recipe_food, class:"form-control", placeholder:"材料をここに" %>
   <%= f.label :body, '作業工程'%>
   <%= f.text_area :recipe_body, class:"form-control", placeholder:"作業工程をここに" %><br>
   <%= f.submit "レシピ投稿", class: "btn btn-warning" %>
 <% end %>

collection_check_boxesというViewヘルパー関数を使って複数選択できるチェックボックスを実装しています。

app/controllers/recipe_controller.rb
class RecipesController < ApplicationController
def new
    @recipe = Recipe.new
  end

  def create
    @recipe = Recipe.new(recipe_params)
    @recipe.user_id = current_user.id
    @recipe.save!
    redirect_to recipe_path(@recipe)
  end

  private
  def recipe_params
    params.require(:recipe).permit(:recipe_title, :recipe_body, :image, :recipe_food, category_ids: [])
  end
end

category_ids はチェックボックスの各チェックに関連付けられるIDが入ります。

app/views/recipes/index.html.erb
<% @recipes.each do |recipe| %>
・
・
・
  <% recipe.categories.each do |category| %>
    <%= category.category_name %>
  <% end %>
<% end %>

上記の記述で、冒頭で表示した2枚目の画像のようにcategory_nameが表示されます。

最後に

今回多対多のアソシエーション、collection_check_boxesメソッドについての実装をしました!
collection_check_boxesメソッドに関してはまだ理解が浅いので、もう少し深く勉強します:sweat:
長くなりましたが、ここまで見て頂きありがとうございました!!:bow:

参考

https://qiita.com/sho012b/items/3a595fde14516081dff5
https://qiita.com/cawaz3/items/e755a58177212f2aca6c

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

Rubyインストールに伴うMSYS2インストール時のエラーについて

環境

  • Windows 10
  • Ruby 3.0.0

どこで引っかかったか

Rubyをインストールする

RubyInstallerを使ってインストールした
ここは特に問題なし。

MSYS2をインストールする

ここで以下のようなエラーで出てうまくインストールできていないような感じ

エラー: mingw32: "David Macek <david.macek.0@gmail.com>" の署名は信頼されていません
エラー: mingw64: "David Macek <david.macek.0@gmail.com>" の署名は信頼されていません

詳細

C:\Windows\System32>ridk install
 _____       _           _____           _        _ _         ___
|  __ \     | |         |_   _|         | |      | | |       |__ \
| |__) |   _| |__  _   _  | |  _ __  ___| |_ __ _| | | ___ _ __ ) |
|  _  / | | | '_ \| | | | | | | '_ \/ __| __/ _` | | |/ _ \ '__/ /
| | \ \ |_| | |_) | |_| |_| |_| | | \__ \ || (_| | | |  __/ | / /_
|_|  \_\__,_|_.__/ \__, |_____|_| |_|___/\__\__,_|_|_|\___|_||____|
                    __/ |           _
                   |___/          _|_ _  __   | | o __  _| _     _
                                   | (_) |    |^| | | |(_|(_)\^/_>

   1 - MSYS2 base installation
   2 - MSYS2 system update (optional)
   3 - MSYS2 and MINGW development toolchain

Which components shall be installed? If unsure press ENTER [1,3] 3

> sh -lc true
MSYS2 seems to be properly installed
Install MSYS2 and MINGW development toolchain ...
> pacman -S --needed --noconfirm auto略ds-git
エラー: mingw32: "David Macek <david.macek.0@gmail.com>" の署名は信頼されていません
エラー: mingw64: "David Macek <david.macek.0@gmail.com>" の署名は信頼されていません
エラー: msys: "David Macek <david.macek.0@gmail.com>" の署名は信頼されていません
エラー: データベース 'mingw32' は無効です (無効または破損したデータベース (PGP 鍵))
エラー: データベース 'mingw64' は無効です (無効または破損したデータベース (PGP 鍵))
エラー: データベース 'msys' は無効です (無効または破損したデータベース (PGP 鍵))
Install MSYS2 and MINGW development toolchain failed
Installation failed: pacman failed

   1 - MSYS2 base installation
   2 - MSYS2 system update (optional)
   3 - MSYS2 and MINGW development toolchain

Which components shall be installed? If unsure press ENTER []

どうやって解決したか

  • MSYS2を更新
  • 再度ridk installを実施

という感じで進めました。

Rubyをインストールしたフォルダ内にあるMSYS2.exeを実行する

C:\Ruby30-x64\msys64\msys2.exe

表示されたコマンドラインでMSYS2を更新

以下issueを参考に、↓のコマンドを実行
https://github.com/msys2/MINGW-packages/issues/240

pacman-key --init
pacman-key --populate msys2
pacman-key --refresh-keys

この後↓を複数回実施する

pacman -Syuu

再度ridk installを実施したら、通りました。
やったぜ

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

devise x omniauth-twitter x bootstrap-social にてTwitterサインインボタンを出力する

めっちゃ苦戦した。ポイントは link_to の文字列をなくしてdo-endで囲み、中にspanを置くことと、'twitter`が文字列でなくシンボルなこと。

## app/views/devise/shared/_links.html.erb
<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %> 
    <% puts provider.class %> 
    <% if provider == 'twitter'.to_sym %>
      <%= link_to omniauth_authorize_path(resource_name, provider), class: "btn btn-block btn-social btn-twitter" do %>
        <span class="fab fa-twitter"></span>Sign in with Twitter
      <% end %>
    <% else %>
      <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
    <% end %>    
  <% end %>
<% end %>

bootstrap-socialはGithubのbootstrap-social.cssapp/assets/stylesheets配下に配置しようとしたが、bootstrap自体の読み込みをapp/javascript/src/application.scssでやっていたので、scssファイルとして同階層に配置し@import 'bootstrap-social.scssとしたものの、読み込み順が違ったりBootstrapのバージョンなのか、Fontawesome5を使っているからなのかレイアウトが崩れたりする。

結局、必要なCSSを Developer Tool を開きながら抜き出す形に‥。つまり言いたいのはTwitterボタンのデザイン適用方法はここのBootstrapのインストール方法によるので何とも言えないってこと。色々と非効率だし美しくない‥。

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

cannot load such file -- nokogiri/nokogiri (LoadError) を解決する方法

この記事では、
cannot load such file -- nokogiri/nokogiri (LoadError) を解決する方法
について書いていきます。

あくまで、僕自身が実際に行った方法ですので、全員がうまくいくとは限りません。

ですが、少しでも参考にしていただけたら幸いです。

解決法

実際のエラー

$ rails s
require': cannot load such file -- nokogiri/nokogiri (LoadError)

何行もあるエラーの最後にこのような文が記載されていました。
gem install nokogiriなどを行っても変わりませんでした。

$ which ruby
/usr/bin/ruby

which ruby でrubyの場所を調べたところ、
/usr/bin/ruby に存在している事がわかりました。

$ [[ -d ~/.rbenv  ]] && \
  export PATH=${HOME}/.rbenv/bin:${PATH} && \
  eval "$(rbenv init -)"

$ which ruby
/Users/XXXX/.rbenv/shims/ruby

このようにrubyの場所を変更します。

$ bundle install --path vendor/bundle

最後にbundle installを行うことでエラーが解消されると思います。

まとめ

$ [[ -d ~/.rbenv  ]] && \
  export PATH=${HOME}/.rbenv/bin:${PATH} && \
  eval "$(rbenv init -)“

$ bundle install --path vendor/bundle

この2つで僕自身の環境では治せました。
それぞれ環境は違うと思うので、参考程度によろしくお願いいたします。

質問や訂正等ございましたら、コメントの方でお願いできればと思います。
最後まで読んでいただきありがとうございました。

参考

cannot load such file -- nokogiri/nokogiri (LoadError) のエラーを解決するまで。 - Qiita

注意

この記事は、プログラミング初学者が書いており、内容に誤りがある場合がございます。
あしからずご了承願います。
また、誤りにお気付きの場合にはご指摘いただけると幸いです。
よろしくお願いいたします。

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

Railsでdeviseを使うときに設定すること【超初心者】

自分用。練習用に作るときに設定することの備忘録。

目次

1.導入
2.ユーザーテーブルの作成
3.マイグレーションファイルの編集
4.コントローラへの記述追加
5.devise.rbの記述追加
6.ルートに記述追加
7.ログイン画面を編集する
8.ログアウト機能を作る
9.サインイン画面を編集する

1.導入

deviseをインストールするためGemfileにdeviseを記載

Gemfile
gem 'devise'

ターミナルにてインストール

tarminal
$ bundle install
$ rails g devise:install

2.ユーザーテーブルの作成

tarminal
$ rails g devise User

Userがモデル名

3.マイグレーションファイルの編集

追加することがあれば追加する。id,password,emailは標準装備。今回はname,age,gender,introduction,profile_image_idを追加する

db/migrate/202102~~01_devise_create_users.rb
()
# t.datetime :locked_at
      t.string :name
      t.integer :age
      t.integer :gender
      t.text :introduction
      t.string :profile_image_id #ここまで追加
      t.timestamps null: false
    end
()

そしたらターミナルでrails db:migrateを行う

4.コントローラへの記述追加

before_action :authenticate_user!はonlyだと記述したものだけ通す。exceptは記述したものだけ通さない。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :authenticate_user!, only: [:top, :index, :show]
  before_action :configure_permitted_parameters, if: :devise_controller?

  def after_sign_in_path_for(resource)
    ログイン後に遷移させたい所_path
  end

  def after_sign_out_path_for(resource)
    ログアウト後に遷移させたい所_path
  end

  protected

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

5.devise.rbの記述追加

ログイン時に必要な項目を変えたい場合に変更。変更しなくても使える。

config/initializers/devise.rb
49行目ぐらい
  # You can also supply a hash where the value is a boolean determining whether
  # or not authentication should be aborted when the value is not present.
  # config.authentication_keys = [:email]

これをコメントアウトを外してemailを変える
  connfig.authentication_keys = [:name]

6.ルートに記述追加

順番に注意!順番を間違えるとエラーになったり無限に繰り返したりする。

config/routes.rb
  devise_for :users
  root to: 'homes#top'

7.ログイン画面を編集する

tarminal
$ rails g devise:views

出来たら勝手にできたログイン(サインイン)画面を編集

app/views/devise/registrations/new.html.erb
<%= form_with model: @user, url: user_session_path, local: true do |f| %> #変更

  <div class="field"> #追加
    <%= f.label :name %>
    <%= f.text_field :name, autofocus: true %>
  </div>
(略)

8.ログアウト機能を作る

置きたいところ.erb
  <body>
    <% if user_signed_in? %>
      <li>
        <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
      </li>
   #追加したいものを書く
    <% else %>
      <li>
        <%= link_to "新規登録", new_user_registration_path %>
      </li>
      <li>
        <%= link_to "ログイン", new_user_session_path %>
      </li>
   #追加したいものを書く
    <% end %> 
    <%= yield %>
  </body>

9.サインイン画面を編集する

合ってるか不安

app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_with model: @user, url: user_registration_path, id: 'new_user', class: 'new_user', local: true do |f| %> #編集
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field"> 
    <%= f.label :name %>
    <%= f.text_field :name, autofocus: true %>
  </div>
  <div class="field">
    <%= f.label :email %>
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>
  <div class="field">
    <%= f.label :age %>
    <%= f.number_field :age, autofocus: true, min: 1, max: 100 %>
  </div>
  <div class="field">
    <%= f.label :gender %>
  <%= f.select :gender, [ ["男性",1], ["女性", 2], ["未選択", 3 ] , prompt: "性別を選択" %>
(略)

とりあえずこれで一通り。
記録をし忘れているところがあったら編集していく所存。
間違えていないか不安なので見直す。

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

【Rails】アソシエーションで紐づいたレコードを自動保存

概要

中級者は知ってて当然なので、初心者向けです?

アソシエーションで関連付けられたモデルを保存する時に、片方のレコードに外部キーが設定されているせいで、「保存処理を2回に分けなきゃ」と思ったことはないですか?(保存するまでidが確定しないので)

そんなときどうするのというお話です

※ 自分もかつて良くないコードを書いていたので、同じような初心者の参考になってもらえれば幸いです

具体例

例えば本のモデル(Book)と著者のモデル(Author)があると想定します
book has_many authorsの関係)

Bookモデル?

カラム 補足
id integer
title string

Authorモデル:man_tone1:

カラム 補足
id integer
book_id integer 外部キー
name string

このとき初心者がやりがちなのが以下の書き方

books_controller
# createアクション

book = Book.new(book_params)
if book.save
  author = Author.new(book_id: book.id, name: author_params[:name])
    if author.save
      # 保存後の処理
    else
      # 失敗時の処理
    end
else
  # 失敗時の処理
end

# book_params, author_paramsの定義は省略してます

これだと以下のようにデメリットが大きいです

  • if文の階層が深くなる
  • bookだけ保存されてauthorが保存されない場合が出てしまう

早期リターンやtransactionでこれらを回避することもできますが、わざわざそんな面倒なことをしなくて済む方法があるのです✨(以下)

books_controller
# createアクション

book = Book.new(book_params)
author = book.build_author(author_params)
return ['保存成功時の処理'] if book.save

# 失敗時の処理

この場合bookの保存が成功した場合はauthorの保存も保証してくれます(片方だけ保存されることはない)

当然外部キーは関連付けられて保存されます
すっきりしていて見やすく、ファットコントローラ対策にもなります?
※ 早期リターンでリファクタもしてます

モデルの関係性が、belongs_tohas_manyとで書き方が変わるので注意が必要です(以下参照)

belongs_toで関連付けられたモデルのbuild

book.build_author

has_oneでの関連付けもこちらになります

has_manyで関連付けられたモデルのbuild

author.books.build

  • 1つだけ紐づくモデル
    => build_[モデル名の単数形]

  • 複数紐づくモデル
    => [モデル名の複数形].build

と覚えとけば良さそうです?
build_複数形にするとインスタンスが複数buildされるイメージがつくからこのように分けたんですかね?)

参考

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

rails db:migrateするとStandardError: An error has occurred, all later migrations canceled:のエラーコード発生

①内容

deviseをinstallした後のrails db:migrateをしたところStandardError: An error has occurred, all later migrations canceled:のエラーコード発生。


②結論

config/database.ymlのencoding: utf8mb4 ⇒ utf8にした事で解消。


③詳細

①ログイン機能実施の為、deviseのinstall実行
         
②rails g devise user(userモデルを作成)
         
③rails db:migrate(マイグレーションの実行)
         
④rails db:migrate:statusで確認すると、、、

 Status   Migration ID    Migration Name
--------------------------------------------------
  down    20210215130947  Devise create users

◉ここでUPになっていないことに気付く。

⑤rails db:migrate:resetもNG

⑥調査しconfig/database.ymlのencoding: utf8mb4 ⇒ utf8にした事で解消。

⑦その後rails db:migrate:reset ⇒ rails db:migrateの順でターミナル実行

⑧rails db:migrate:status

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210215130947  Devise create users

こちらで解消。

今回も勉強になりました!!

初心者ですが、解消方法などを載せていきます。

間違い等ございましたら、ご指導ご鞭撻の程、よろしくお願いします。

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

rspecのテストでFactorybotが代入されなかった!?

WHY

rspecでテストコードの記述中、Factorybotで二つの変数を定義しようとしたところ、片方は代入されるのに、片方はされなかったことが起きたのでアウトプットしていきます。


問題

今回はテストコードを記述し、いざ実行!とするとこのようなエラー文がずらり、、、
fact.jpg

対処

最近エラーを解除するのがとても楽しくなってきた自分ですが、とりあえず最初に目が止まったのは
「 Failure/Error: user = FactoryBot.create(:user)」
というエラー、
まぁ調べるまでもなくuserに入っていないという意味でしょう、、しかし、コードをみてみると、

before do
    item = FactoryBot.create(:item)
    user = FactoryBot.create(:user)
    @sample = FactoryBot.build(:sample, user_id: user.id, item_id: item.id)
end

記述は上のitemと同じ、記述は間違っていない、、、となるとFactoryBot内がおかしいのか?とおもいましたが、
UserのFactoryBotは他のspecファイルでも使用しており、そこでは問題なく動作している、、、

続いてエラー文の下の方をみてみると
「Failure/Error: _query(sql, @query_options.merge(options))」
という文字が、調べてみるとテストではたまに起きて、変数が代入しきる前にテストが実行されたりすることで起こるのだそう、、、
解決策としてはsleepというものを使う。
なので書いてみた

before do
    item = FactoryBot.create(:item)
    user = FactoryBot.create(:user)
    @sample = FactoryBot.build(:sample, user_id: user.id, item_id: item.id)
    sleep(1)
end

これでテストは1秒ごとに実行される。

しかしエラーは治らなかった、となればやっぱり代入がされない何かの理由がある!!
ということで次に目が行ったのは
「Validation failed: Email has already been taken」という文字

直訳すると「メールはすでに使われている」というもの、
deviseを使っているので一意性があるのでしっかりバリデーションが仕事してくれています!

解決

ということでFactorybotをみにいくと、

FactoryBot.define do
  factory :user do
    nickname              { 'Test' }
    email                 { 'test@test' }
    password              { 'test111' }
    password_confirmation { password }
  end
end

このEmailに注目!
一意性のある値をテストするときはFakerを指定しなければならないのでemailの値を修正します!!

email                 { Faker::Internet.free_email }

その後テストはエラーなく無事解決しました!!

なお、「Failure/Error: _query(sql, @query_options.merge(options))」に関してはその後も出たので、
sleepはそのままにしています。

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

【AWS】デプロイ後にadminユーザーを追加する方法〜seedsファイルの実行〜

なんの話し?

AWSにデプロイした後にadminユーザーを追加する方法がわからなくて悩んだ結果の話しです。
開発環境ではseedsファイルにadmin: trueの記述をしていた。

awsにデプロイした後にadminユーザーを追加したい
などで検索するとIAMアカウント関連の結果ばかり出てきてしまい、なかなか欲しい情報にたどり着けない。
そもそもseedsを追加した時のコマンドは開発環境で下記のように実行していた。

開発環境のとき

% rails db:seed

そしてHerokuにデプロイした時にも同じことで悩み、実行したコマンドがこちら。

Herokuにデプロイしたとき

# アプリケーションのディレクトリで実行

% heroku run rails db:seed

EC2のリポジトリでRailsを起動するときのコマンドを見ていたら、
rails~コマンドを使えれば追加できるのではないか?」と考え、実行してみた。

AWSにデプロイしたとき

# EC2のリポジトリ内で実行

$ rails db:seed RAILS_ENV=production

これでadminユーザーが追加された。

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

【Rails】【nested_form】追加された要素自体のIDを取得する方法

結論

以下のように記述します。

 fields_for :items do |item|
  item.object_id #=> 「69870471010820」
 end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Postgresがサーバーに接続できない問題(could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/tmp/.s.PGSQL.5432"?)

環境

❯ sw_vers
ProductName:    macOS
ProductVersion: 11.2.1
BuildVersion:   20D75

❯ ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin20]

❯ rails -v
Rails 6.1.2.1

❯ postgres --version
postgres (PostgreSQL) 13.2

状況の説明

macのローカル環境をリセットするために、Homebrewやその他諸々をアンインストール後、再び同じ環境を構築し、bin/rails sでサーバーをスタートさせると以下の画像のようなエラーが出てきた。brew services restart postgresqlなどやっても変わらず。
スクリーンショット 2021-02-20 13.18.54.png

原因

以前のpostgresのプロセスが残ってしまっているのが原因。バージョンアップの際にしばしば起こるらしい。

解決策

rm -rf /usr/local/var/postgres && initdb /usr/local/var/postgres -E utf8でデータベースをリセットした後、brew services start postgresqlで接続できるようになった。データベースの情報はなくなってしまうので注意。

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

PostgreSQLで全ての行の特定のカラムの値を一括セットする方法

Railsプロジェクトでpostsテーブルにuser_idを持たせる必要性がでた。カラムは無事追加できたが、既存のレコードに1個ずつユーザーIDを手打ちする(Posticoを使ってます)のはめんどくさい。SQLは書いたことなかったが、以下のシンプルなコードでいける(かならず Export CSV なりしてバックアップをとってから実施されることをおすすめします)。

UPDATE posts SET user_id=1

https://stackoverflow.com/questions/10364200/setting-value-for-one-column-of-all-records-in-table

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

【Rails】ActiveRecord::ConnectionNotEstablished を解消する

1.はじめに

railsでアプリを作成し、サーバーを立ち上げrails sを行い、http://localhost:3000/XXにアクセスしました。
すると、ActiveRecord::ConnectionNotEstablishedと表示されました。

2.使用環境

  • mac.os バージョン10.15.6
  • Ruby 2.6.6
  • Rails 6.0.3.5
  • psql (PostgreSQL) 12.6

3.実際のエラー

ActiveRecordのエラー

4.試したこと

4.1 データベースの確認

error.rb
# 利用可能な全てのデータベースを一覧表示する
hogehoge@hogenoAir sample_app % psql -l
psql: error: could not connect to server: No such file or directory
        Is the server running locally and accepting
        connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

4.2 postmaster.pidの削除

hogehoge@hogenoAir sample_app % rm /usr/local/var/postgres/postmaster.pid

こちらを試すと、うまくいきました。

補足 /usr/の場所

macの場合、Finderの移動タブから、フォルダへ移動を選択し、/usr/と入力すると表示されます。

5.まとめ

今回はリンクの5番に近い状況で、
① パソコンがフリーズ
② 全て保存・終了して再起動
③ 表題のエラーが表示される ...という流れでした。

②で全て終了でいたはずが、うまく終了されなかったようです。
傷がついてしまったpostmaster.pidを削除することで、解決できました。

6.参考リンク

1:PostgreSQL 9.1.5文書
2.Postgresqlに接続できなくなった時
3.Postgres could not connect to server
4.【Mac】Finderから /usr / local ディレクトリを参照する
5.Mac+HomebrewでPostgreSQLが起動しない場合の対応

7.最後に

記事の感想や意見、ご指摘等あれば伝えていただけるとありがたいです。
読んでいただき、ありがとうございました。

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

Rails 6.0 でカラムの型を string -> integer に変更する方法

ググったら'integer USING CAST(column_name AS integer)'みたいなやり方がでたけどこれは古い書き方のようでエラーが出た。Rails6で上手く行ったのは下記。

20210219164606_change_user_id_in_posts.rb
class ChangeUserIdInPlatforms < ActiveRecord::Migration[6.0]
  def change
    change_column :posts, :user_id, :integer, using: 'user_id::integer'
  end
end

https://stackoverflow.com/questions/3537064/rails-migration-to-convert-string-to-integer

役立ちそうなコマンド

ただしrollbackやマイグレーションファイルの削除は特にチーム開発ではご法度。やらないことを推奨する。

# 戻したいとき
## マイグレーションのバージョン確認
$ rails db:version
## バージョンを戻す(ただし非推奨)
$ rails db:rollback step=2
$ rails db:rollback 20210219164606_change_user_id_in_posts.rb

ただしchangeメソッドで定義した操作のうちremove_columnchange_columnはrollbackすることはできない。そのときはupメソッドとdownメソッドの2つ(migrateはup, rollbackはdown)にマイグレーションファイルを置き換える必要がある(rails db:migrateした後にマイグレーションファイルを変更してrails db:rollbackするみたい‥怖すぎるけど)。

class removeColumnHoge < ActiveRecord::Migration
  def change
    remove_column :hoge, :fuga
  end
end
class removeColumnHoge < ActiveRecord::Migration
  def up
    remove_column :hoge, :fuga
  end

  def down
    add_column :hoge, :fuga, :string
  end
end

http://tanihiro.hatenablog.com/entry/2014/01/10/182122

その他

投稿とユーザーを紐付ける場合、投稿モデルに持たせるuser_idはintegerじゃなくてreferrenceで持つべきというQiitaもあった。Progateではintegerになってたけど

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

評価機能 + 自己完結型多対多アソシエーション

はじめに

前回作成したrails 評価機能(数字のみ)(https://qiita.com/inoponA/items/60384b454e16024295ff)を投稿しました
まぁ当然と言っちゃ当然だがこれだとユーザー、一人一人の評価が評価される訳ではなくでのページでも同じ評価がなされてしまう

と言う訳で、個別表示+α機能を備えた評価機能を作りました

自己完結多対多とは?

まず個別に表示するには?と考えた時、一対多だとどのユーザーが評価した?しか分からない訳です
0ccf604cb6229dccbee2bd287f8e5832.png

よって、一対多のアソシエーションではなく、多対多のアソシエーションを組むことによって誰が誰に評価したかわかりやすくする
例:ラーメン店のレビュー
19c1fc72fe2f2c9dd5676c1421bb6821.png

これでレビューは出来るのだが、今回が人が人に評価する図式となるので上記ものだと少し違う
少しややこしいのだが、userテーブル一つにreviweテーブルが中間テーブルと言う形になる
7759d2e0da9af10219ea667fd740b7f6.png

今回はこの自己完結多対多を利用します

DB,カラム

_create_reviews.rb

      t.integer :speedy
      t.integer :kindness
      t.integer :frantically
      t.references :reviewer, null: false, foreign_key: { to_table: :users }
      t.references :reviewing, null: false, foreign_key: { to_table: :users }
      t.timestamps

注目していただきたいのはreviewerとreviewingと言うカラムが作らているがこれはforeign_key: { to_table: :users }でuserテーブルを外部キーとして指定している
でもこれだけだとただのreviewer、reviewingカラムができただけであるので下記のモデルにこのカラムが擬似的なuser.idだとするようにしてあげる必要がある

reviewモデル

class Review < ApplicationRecord
  belongs_to :reviewer, class_name: "User"
  belongs_to :reviewing, class_name: "User"
end

ここできもなのがclass_nameオプションである
これによって同じuser_idをreviewer_id、reviewing_idと言う形で分ける事が可能になるので誰が誰にと言う図式が完成する

コレで中間テーブルは完成
でも中間テーブルがあると言うことは多の部分も作成していくことになる

user.rb

  has_many :active_reviews, class_name: "Review",foreign_key: :reviewing_id
  has_many :reviewings, through: :active_reviews, source: :reviewer
  has_many :passive_reviews, class_name: "Review",foreign_key: :reviewer_id
  has_many :reviewers, through: :active_reviews, source: :reviewing

  def followed_by?(user)
    passive_reviews.find_by(reviewing_id: user.id).present?
  end

一つ一つ説明します
まずactive_reviewsとかpassive_reviewsなんやねんと思ったでしょうが、reviewと言う中間テーブルを扱ってもいいのだがこれだと後述の評価を一人一回と言う制限をする為には少々不都合なので
”レビューする側のテーブルとされる側のテーブル”とclass_nameで名前を変える

要するに
- active_reviewsはレビューされる側
- passive_reviewsはレビューする側
である

  def followed_by?(user)
    passive_reviews.find_by(reviewing_id: user.id).present?
  end

はpassive_reviewsのレビューされる側からレビューされるIDを探せと言うコードを記述し一人一回しか評価できないようにします

routes.rb

  resources :users do
    resources :reviews, only: :create 
  end

ユーザーにレビューをネストさせる
レビューされる為にはユーザーのIDが欲しいのでこれをする必要がある

controller-review

class ReviewsController < ApplicationController

  def create
    @review = Review.create(review_params)
    if @review.save
      redirect_to root_path
    else
      render :show
    end
  end

  private
  def review_params
    params.require(:review).permit(:speedy, :kindness, :frantically).merge(reviewer_id: current_user.id, reviewing_id: params[:user_id])
  end
end

言うまでもないが、mergeで各ユーザーIDも保存している
- reviewer_idは評価した人(評価の人)
- reviewing_idはされた人(ユーザーページの人)

controller-user

def show
  @posts = @user.posts
  @review = Review.new
  @kindness = Review.where(reviewing_id: params[:id]).average(:kindness)
  @speedy = Review.where(reviewing_id: params[:id]).average(:speedy)
  @frantically = Review.where(reviewing_id: params[:id]).average(:frantically)
  if @kindness != nil && @speedy != nil &&  @frantically != nil
    @comprehensive = (@kindness + @frantically + @speedy) / 3
  else 
    @speedy = 0
    @kindness = 0
    @frantically = 0
    @comprehensive = @kindness + @frantically + @speedy
  end

今回はshowページで一連の流れを行っているのでshowに必要な事を記述しています
@review = Review.newでレビュー内容を生成
以下は平均値を求める内容です
Review.where(reviewing_id: params[:id])でされた人のIDを探しその中でも特定のカラムの平均が欲しかったので.averageを利用しました

view1 表示側

        - if @user.id != current_user.id
          - if current_user.followed_by?(@user)
            .UserEvaluate
              評価は一人一回です
          - else
            .UserEvaluate
              %button.EvaluateBtn
                = "#{@user.name}さんを評価する"
      = render "evaluate"
      .UserStatus
        他のユーザーの評価
        .UserStatus__Kindness
          = icon('fa','smile') + "親切度 : #{@kindness.round(1)} point"
        .UserStatus__speedy
          = icon('fa','shipping-fast') +"迅速さ : #{@speedy.round(1)} point"
        .UserStatus__frantically
          = icon('fa','redo')+"repeat : #{@frantically.round(1)} point"
        .UserStatus__comprehensive
          = icon('fa','star')+"総合力 : #{@comprehensive.round(1)} point"
      .UserEdit

1行目:投稿者と閲覧者が同一ユーザー出ない時に分岐するIF文の作成
同一だと評価できない図式に
2行目:followed_by?メソッドをビューで利用してもしログインしているユーザーがレビューした側にいたら評価は一人一回ですと言う表示をしていなければ評価出来るように分岐する
ビューはこれだけ

view2 入力側

.EvaluateSide
  .EvaluateSide__EvaluateBox
    = form_with model:[@user,@review],local: true do |f|
      .close
        = icon('fas','times')
      .UserEvaluate
        .UserEvaluate__title
          = "#{@user.name}さんを評価しよう"
        .UserEvaluate__UserKindness
          = "#{@user.name}さんをは親切でしたか?"
          .form
            .form__KindnessGood
              = f.radio_button :kindness,"3",id: "kaidness-good" ,class: "UserKindnessEvaluate"
              .form__KindnessGood__good
                = f.label "親切" ,for:"kaidness-good"
            .form__KindnessUsually
              = f.radio_button :kindness,"2",id: "kaidness-usually" ,class: "UserKindnessEvaluate"
              .form__KindnessUsually__Usually
                = f.label "普通" ,for:"kaidness-usually"
            .form__KindnessBad
              = f.radio_button :kindness,"1",id: "kaidness-bad" ,class: "UserKindnessEvaluate"
              .form__KindnessBad__bad
                = f.label "不親切" ,for:"kaidness-bad"
        .UserEvaluate__UserSpeedy
          = "#{@user.name}さんをは迅速な対応でしたか?"
          .form
            .form__SpeedyGood
              = f.radio_button :speedy, "3",id: "speedy-good" ,class: "UserKindnessEvaluate"
              .form__SpeedyGood__good
                = f.label "迅速" ,for:"speedy-good"
            .form__SpeedyUsually
              = f.radio_button :speedy, "2",id: "speedy-usually" ,class: "UserKindnessEvaluate"
              .form__SpeedyUsually__Usually
                = f.label "普通" ,for:"speedy-usually"
            .form__SpeedyBad
              = f.radio_button :speedy, "1",id: "speedy-bad" ,class: "UserKindnessEvaluate"
              .form__SpeedyBad__bad
                = f.label "遅い" ,for:"speedy-bad"
        .UserEvaluate__frantically
          = "#{@user.name}さんにもう一度お願いしたいですか?"       
          .form
            .form__franticallyGood
              = f.radio_button :frantically, "3",id: "frantically-good" ,class: "UserKindnessEvaluate"
              .form__FranticallyGood__good
                = f.label "頼みたい" ,for:"frantically-good"
            .form__FranticallyUsually
              = f.radio_button :frantically, "2",id: "frantically-usually" ,class: "UserKindnessEvaluate"
              .form__FranticallyUsually__Usually
                = f.label "普通" ,for:"frantically-usually"
            .form__FranticallyBad
              = f.radio_button :frantically, "1",id: "frantically-bad" ,class: "UserKindnessEvaluate"
              .form__FranticallyBad__bad
                = f.label "これっきり" ,for:"frantically-bad"
        .UserEvaluate__SendBox
          = f.submit "#{@user.name}さんを評価する",class:"send-btn"

ざっと長いコードになってしまったが重要なのは
= form_with model:[@user,@review],local: true do |f|
の一文である
ただそれだけ
ちなみにこれは逆だとエラーが起こる、私はこれの解決に時間がかかってしまったがどっちは親かって考えたら普通はわかるだろうと思う?

最後に

かなり解釈が間違えているかもですがそこはご愛敬お願いします笑

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

simpackerでRails + React + Typescript環境構築

はじめに

simpackerとは

webpackerを使わず、シンプルなwebpackでRailsを開発するgem。
クックパッド技術部の方が作られたgemです。
https://github.com/hokaccha/simpacker
Simpacker: Rails と webpack をもっとシンプルにインテグレーションしたいのです
Githubと、このクックパッドの開発者ブログを見れば、simpackerが何なのかほとんどわかると思います。

なぜsimpacker?

Rails6以降、webpackerが標準搭載になっていますが、webpackerは独自のDSLになっており、webpack本来の設定などが見えなくなっています。簡単にwebpackを扱えますが、設定のカスタマイズには独自DSLを学ぶコストがついてきます。またwebpackの細かい設定などは調整しにくかったりできないこともあるので、純粋なwebpackを使いたいならsimpackerの導入をおすすめします。
またwebpackerの独自DSLを学ぶなら、webpackそのものを学んだ方が今後の開発に活かせる気がします。私は初めて現場で触れたのがsimpackerの方で、webpackerのことはよくわかってなかったりします。笑

Rails + React + TypeScriptで環境構築

バックエンドはRails、フロントエンドはReact + TypeScriptで構成された開発手順をメモしたいと思います。

開発環境

ruby 3.0.0
rails 6.1.1
yarn 1.22.10
tsc 4.1.3

手順

グローバルを汚さないRails環境構築やMySQLのコンテナ化は別記事に解説してますので、よければ参考にしてください。
Dockerでコンテナ化したMySQLを使用してRails環境構築
Railsの環境構築(グローバル環境を汚さずに)

railsプロジェクト立ち上げ
bundle exec rails new my_app --skip-javascript
bundle exec rails db:create

webpackerを入れたくないので、--skip-javascriptを付けます。

simpackerをGemfileに追加
Gemfile
gem 'simpacker'
gemインストール
bundle install
simpacker初期化コマンド実行
bundle exec rails simpacker:install
React, TypeScript、必要なパッケージをインストール
yarn add -D react react-dom
yarn add -D typescript ts-loader

yarnを使っていますが、npmでも問題ありません。
ts-loaderはtypescriptをjavascriptにトランスパイルするためのパッケージです。

webpack.config.jsを作成

ここにwebpackの設定を書いていきます。
純粋なwebpackと全く同じ書き方です。

webpack.config.js
const path = require("path");
const WebpackAssetsManifest = require("webpack-assets-manifest");

const { NODE_ENV } = process.env;
const isProd = NODE_ENV === "production";

module.exports = {
  mode: isProd ? "production" : "development",
  devtool: "source-map",
  entry: {
    application: path.resolve(__dirname, "app/frontend/js/packs/application.tsx"),
  },
  output: {
    path: path.resolve(__dirname, "public/packs"),
    publicPath: "/packs/",
    filename: isProd ? "[name]-[hash].js" : "[name].js",
  },
  resolve: {
    extensions: [".js", ".ts", ".jsx", "tsx"],
  },
  module: {
    rules: [
      {
        test: /\.(js|ts|jsx|tsx)$/,
        exclude: /node_modules/,
        use: [
          {loader: "ts-loader"}
        ]
      }
    ]
  },
  plugins: [
    new WebpackAssetsManifest({
      publicPath: true,
      output: "manifest.json",
    }),
  ],
};

const path = require("path");
node.jsのpathモジュールを読んでます。

const WebpackAssetsManifest = require("webpack-assets-manifest");
manifest.json
を生成してくれるパッケージです。

const { NODE_ENV } = process.env;
こちらで任意の環境変数を読み込んでいます。

mode:
modeによって出力ファイルの形式が変わります。productionだと圧縮され、develomentだとみやすく整形されて出力されます。

devtool:
ソースマップを指定できます。ソースマップを有効にすると、ブラウザコンソールでエラーを確認するときに、エラー箇所を特定できるため必須だと思います。

entry:
webpackに読み込ませるエントリポイントを指定します。
path.resolve(__dirname, "")と言う表記は環境に依存しない絶対pathを取得できるっぽいです。

output:
ファイルに出力先を指定します。
publicPathは本番環境での解決pathを指定しています。
railsではデフォルトでpublic配下がドキュメントルートなので、/packs/を指定しています。

extensions:
importするファイルの拡張子を省略できます。同じファイル名の異なる拡張子ファイルが存在した場合、配列の先頭のものが読み込まれます。

import File from '../path/to/file';

rules:
トランスパイルするローダーの設定を書きます。
test:で対象ファイルを指定します。
useで使用するローダーをしてします。複数書いた場合は後ろから実行されます。

WebpackAssetsManifest:
manifest.jsonを作成するwebpack用ライブラリです。

webpackの設定はやはり公式を参照すべきだと思います。
https://webpack.js.org/concepts/
こちらも充実しています。
webpack 4 入門 - Qiita

tsconfig.jsonを作成

typescriptの設定を書いていきます。

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "jsx": "react",
    "allowJs": true,
    "moduleResolution": "node",
    "sourceMap": true,
    "strict": true,
    "noImplicitAny": false,
  },
  "include": [
    "app/frontend/js/**/*"
  ],
  "exclude": [
    "**/*.(spec|test).ts",
    "**/setup.jest.ts",
  ]
}

target:
出力するjsのバージョンを指定します。

module:
使用するモジュールを指定します。

targetやmoduleについてまだ詳しく理解していないので、今後勉強していきたいと思ってます。

jsx:
reactを使用する場合、reactを指定します。

allowJs:
trueでjsファイルもトランスパイルしてくれます。

moduleResolution:
とりあえずnodeにしておけばいい?

strict:
全ての型チェックを有効にします。

tsconfig.jsonについては、
公式にコンパクトにまとまっています。
https://typescript-jp.gitbook.io/deep-dive/project/compilation-context/tsconfig
日本語だと、こちらの記事でかなり詳細に解説してくださっています。
tsconfig.jsonの全オプションを理解する(随時追加中) - Qiita

Reactコンポーネントを作成

エントリーポイントのファイルを作成していきます。

app/frontend/js/packs/application.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';

import Index from '../pages/Index';

const appElement = document.getElementById('app');

if (appElement) {
  ReactDOM.render((
    <Index />
  ), appElement);
}

レンダーするコンポーネント

app/frontend/js/pages/Index.tsx
import * as React from 'react';

interface Props {}

const Index: React.FC<Props> = () => {
  return(
    <div>Hello React</div>
  )
};

export default Index;

これをwebpackでビルドしてjsファイルを出力します

yarn webpack

/public/packs/配下にapplication.js, application.js.map, manifest.jsonが作成されていると思います。

これらをRails側で読み込みましょう。

bundle exec rails g controller Top
app/controllers/top.rb
class TopController < ApplicationController
  def index; end
end

app/views/top/index.html.erb
<div id="app"></div>
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Sample</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
  </head>

  <body>
    <%= yield %>
    <%= javascript_pack_tag 'application' %>
  </body>
</html>

simpackerが良い感じに<%= javascript_pack_tag 'application' %>を解釈して読み込んでくれます。

http://localhost:3000/indexにアクセスしてHello Reactが表示されれば成功です。

終わりに

simpackerはシンプルにwebpackを扱えるようにしてくれます。
ただ、今のRailsのフロント周りのgemはwebpackerを前提に作られているものが多いので、そういうgemを利用する際には、かなり苦労することもありますので、少なからずデメリットも存在します。

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

ActiveHash

ActiveHash

都道府県名一覧やカテゴリーなど「基本的に変更されないデータ」は、データベースに保存する必要性がありません。
ビューファイルなどにそれらのデータを直接書いてしまうと、記述量が多くなり見づらくなります。
そのようなケースでは、ActiveHashを使って実装します。
モデルファイルに直接記述した変更されないデータに対して、ActiveRecordのようなメソッドを用いることができます。

ミニアプリを作りながら使ってみましょう。

% rails _6.0.0_ new active_hash -d mysql

# active_hashに移動
% cd active_hash

# データベースの生成
% rails db:create

Gemを追加します

Gemfile
gem 'active_hash'

住所を管理するaddressモデルを作成しましょう。

% rails g model address

都道府県を管理するprefectureモデルを作成しデータを定義します
モデルの「prefectureクラス」を定義し、ActiveHash::Baseクラスを継承します。
app/modelsディレクトリ配下にprefecture.rbを作成します。

ActiveHash::Base

あるモデル内(クラス内)でActiveHashを用いる際に必要となるクラスです。ActiveHashのGemに定義されていて継承することで、ActiveRecordと同じようなメソッドを使用できるようになります。

class Prefecture < ActiveHash::Base
  self.data = [
    { id: 0, name: '--' },
    { id: 1, name: '北海道' }, { id: 2, name: '青森県' }, { id: 3, name: '岩手県' },
    { id: 4, name: '宮城県' }, { id: 5, name: '秋田県' }, { id: 6, name: '山形県' },
    { id: 7, name: '福島県' }, { id: 8, name: '茨城県' }, { id: 9, name: '栃木県' },
    { id: 10, name: '群馬県' }, { id: 11, name: '埼玉県' }, { id: 12, name: '千葉県' },
    { id: 13, name: '東京都' }, { id: 14, name: '神奈川県' }, { id: 15, name: '新潟県' },
    { id: 16, name: '富山県' }, { id: 17, name: '石川県' }, { id: 18, name: '福井県' },
    { id: 19, name: '山梨県' }, { id: 20, name: '長野県' }, { id: 21, name: '岐阜県' },
    { id: 22, name: '静岡県' }, { id: 23, name: '愛知県' }, { id: 24, name: '三重県' },
    { id: 25, name: '滋賀県' }, { id: 26, name: '京都府' }, { id: 27, name: '大阪府' },
    { id: 28, name: '兵庫県' }, { id: 29, name: '奈良県' }, { id: 30, name: '和歌山県' },
    { id: 31, name: '鳥取県' }, { id: 32, name: '島根県' }, { id: 33, name: '岡山県' },
    { id: 34, name: '広島県' }, { id: 35, name: '山口県' }, { id: 36, name: '徳島県' },
    { id: 37, name: '香川県' }, { id: 38, name: '愛媛県' }, { id: 39, name: '高知県' },
    { id: 40, name: '福岡県' }, { id: 41, name: '佐賀県' }, { id: 42, name: '長崎県' },
    { id: 43, name: '熊本県' }, { id: 44, name: '大分県' }, { id: 45, name: '宮崎県' },
    { id: 46, name: '鹿児島県' }, { id: 47, name: '沖縄県' }
  ]

end

データは、配列にハッシュ形式で格納しています。

マイグレーションファイル変更

class CreateAddresses < ActiveRecord::Migration[6.0]
 def change
   create_table :addresses do |t|
     t.string     :area, null: false
     t.text       :text, null: false
     t.integer    :prefecture_id, null: false
     t.timestamps
   end
 end
end

マイグレーション実行

% rails db:migrate

アソシエーションを設定
住所を表示する際、都道府県を取得できるように、AddresモデルとPrefectureモデル間でアソシエーションを設定しておきましょう。
ActiveHashを用いてアソシエーションを設定する場合は、ActiveHashで定義されているmoduleをモデルに取り込む必要があります。

module(モジュール)

特定の役割を持つメソッドや定数に名前を付けてまとめたものです。moduleはどんなオブジェクトにも取り込んで使うことが可能です。複数のクラスにまたがるメソッドや定数をmoduleとしてまとめることで、コードの肥大化を防いだり、複数クラスの特徴を継承させたいクラスを作成するときに使います。

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

住所は、1つの都道府県に紐付いています。そのため、Addressモデルにはbelongs_toを設定します。

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

class Address < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :prefecture
end

Prefectureモデルのアソシエーションを設定しましょう
1つの都道府県は、たくさんの住所に紐付いています。そのため、Prefectureモデルにはhas_manyを設定します。

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

class Prefecture < ActiveHash::Base
  self.data = [
    { id: 0, name: '--' },
    { id: 1, name: '北海道' }, { id: 2, name: '青森県' }, { id: 3, name: '岩手県' },
    { id: 4, name: '宮城県' }, { id: 5, name: '秋田県' }, { id: 6, name: '山形県' },
    { id: 7, name: '福島県' }, { id: 8, name: '茨城県' }, { id: 9, name: '栃木県' },
    { id: 10, name: '群馬県' }, { id: 11, name: '埼玉県' }, { id: 12, name: '千葉県' },
    { id: 13, name: '東京都' }, { id: 14, name: '神奈川県' }, { id: 15, name: '新潟県' },
    { id: 16, name: '富山県' }, { id: 17, name: '石川県' }, { id: 18, name: '福井県' },
    { id: 19, name: '山梨県' }, { id: 20, name: '長野県' }, { id: 21, name: '岐阜県' },
    { id: 22, name: '静岡県' }, { id: 23, name: '愛知県' }, { id: 24, name: '三重県' },
    { id: 25, name: '滋賀県' }, { id: 26, name: '京都府' }, { id: 27, name: '大阪府' },
    { id: 28, name: '兵庫県' }, { id: 29, name: '奈良県' }, { id: 30, name: '和歌山県' },
    { id: 31, name: '鳥取県' }, { id: 32, name: '島根県' }, { id: 33, name: '岡山県' },
    { id: 34, name: '広島県' }, { id: 35, name: '山口県' }, { id: 36, name: '徳島県' },
    { id: 37, name: '香川県' }, { id: 38, name: '愛媛県' }, { id: 39, name: '高知県' },
    { id: 40, name: '福岡県' }, { id: 41, name: '佐賀県' }, { id: 42, name: '長崎県' },
    { id: 43, name: '熊本県' }, { id: 44, name: '大分県' }, { id: 45, name: '宮崎県' },
    { id: 46, name: '鹿児島県' }, { id: 47, name: '沖縄県' }
  ]

  include ActiveHash::Associations
  has_many :addresses

end

numericality

数値かどうかを検証するバリデーションの一種です。
数値であればデータベースに保存を許可して、それ以外では保存が許可されないようにできます。
今回は、--を保存されないようにしたいので、id: 1以外であれば保存できるように設定します。

class Address < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :perefecture

  validates :area, :text, presence: true

  validates :prefecture_id, numericality: { other_than: 1 } 
end

ルーティングを設定

config/routes.rb

Rails.application.routes.draw do
  root to: 'addresses#index'
  resources :addresses
end

コントローラーとビューを作成

% rails g controller addresses index new

addresses_controller.rbを編集

class AddressesController < ApplicationController

  def index
    @adress = Address.order("created_at DESC")
  end

  def new
    @address = Address.new
  end

  def create
    @address = Address.new(address_params)
    if @address.save
      redirect_to root_path
    else
      render :new
    end
  end

  private

  def address_params
    params.require(:address).permit(:area,:text,:prefecture_id)
  end

end

index.html.erbを編集

<h1>エリア一覧</h1>
<%= link_to "投稿する", new_address_path, class:"post" %>

<% @adress.each do |address| %>
  <div class="address">
    <div class="address-area">
      <%= address.prefecture.name %>
    </div>
    <div class="address-title">
      <%= address.area %>
    </div>
    <div class="address-text">
      <%= address.text %>
    </div>
  </div>
<% end %>

index.cssを以下のように作成し編集してください。

h1 {
  text-align: center;
}

.post {
  display: block;
  width: 150px;
  padding: 10px;
  border: 1px solid white;
  background-color: lightskyblue;
  color: white;
  text-decoration: none;
  text-align: center;
  margin: 0 auto;
  margin-bottom: 20px;
}

.address {
  height: 200px;
  background-color: lemonchiffon;
  display: flex;
  align-items: center;
  flex-direction: column;
  border: 1px solid #fff;
  padding: 20px;
}

.address-area {
  font-size: 16px;
  border: 1px solid;
  padding: 5px;
  background-color: lightblue;
}

.address-title {
  font-size: 20px;
  font-weight: bold;
}

collection_select

データをプルダウン形式で表示することができるメソッドです。

new.html.erbを編集

<%= form_with model: @address, url:addresses_path, local: true do |f| %>
  <div class="address-box">
   投稿する
    <%= f.text_field :area, class:"area", placeholder:"エリア" %>
    <%= f.text_area :text, class:"text", placeholder:"テキスト" %>
    <%= f.collection_select(:prefecture_id, Prefecture.all, :id, :name, {}, {class:"prefecture-select"}) %>
    <%= f.submit "投稿する" ,class:"btn" %>
  </div>
<% end %>

collection_selectは、下記のような順番で記述します。

<%= form.collection_select(保存されるカラム名, オブジェクトの配列, カラムに保存される項目, 選択肢に表示されるカラム名, オプション, htmlオプション) %>
引数 役割
第一引数(保存されるカラム名) :prefecture_id 保存先のカラム名
第二引数(オブジェクトの配列) Prefecture.all 配列データを指定する
第三引数(カラムに保存される項目) id 表示する際に参照するDBのカラム名
第四引数(選択肢に表示されるカラム名) name 実際に表示されるカラム名
第五引数(オプション) {} 今回はオプションの指定なし
htmlオプション {class:"prefecture-select"} 今回はクラス名を付与

new.cssを以下のように作成し編集してください。

.address-box {
  height: 800px;
  background-color: lightseagreen;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.area {
  height: 30px;
  width: 300px;
  margin: 20px;
}

.text {
  height: 100px;
  width: 300px;
  resize: none;
  margin-bottom: 20px;
}

.prefecture-select {
  height: 30px;
  width: 250px;
}

.btn {
  width: 200px;
  margin-top: 20px;
  padding: 15px;
  border: 1px solid white;
  background-color: lightslategray;
  color: white;
}

http://localhost:3000/articles/new にアクセスして
挙動を確認してみましょう。

以上です。

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

【Rails】特定のカラムのオプションだけを変更したい

Rails6.0.0でアプリを作っていて、既に作成済みのテーブルのカラムにオプションの制約をつけ忘れていた。
なので、特定のカラムのオプションだけを追加で設定する方法をまとめる。

現状

現状のマイグレーションファイルは以下のように「null:false」オプションを付け忘れているカラムがある。

class CreateItems < ActiveRecord::Migration[6.0]
  def change
    create_table :items do |t|
      t.string     :name
      t.text       :explain
      t.integer    :price
      t.references :user,            null: false, foreign_key: true
      t.integer    :category_id,     null: false
      t.integer    :item_status_id,  null: false
      t.integer    :shipping_fee_id, null: false
      t.integer    :prefecture_id,   null: false
      t.integer    :delivery_id,     null: false
      t.timestamps
    end
  end
end

手順

①新しいマイグレーションファイルを作る。

「rails g migration ChangeColumnOfItems」とコマンドを打つ(アッパーキャメル)。
コマンドの意味は「Itemsテーブルのカラムを変更せよ」という感じ。

terashimatakaya@MacBook-Air furima-34501 % rails g migration ChangeColumnOfItems
Running via Spring preloader in process 8220
      invoke  active_record
      create    db/migrate/20210220012237_change_column_of_items.rb

②マイグレーションファイルを自分で修正する。

「Railsガイドv6.1」のマイグレーションの章を参考に、以下のように記述した。
意味としては、「itemsテーブルのnameカラムにnull:falseのオプションを追加してね」という感じ。

class ChangeColumnOfItems < ActiveRecord::Migration[6.0]
  def change
    change_column_null :items, :name, false
    change_column_null :items, :explain, false
    change_column_null :items, :price, false
  end
end

③マイグレーションを実行

「rails db:migrate」を実行。

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

sqlcommenterをRuby on Railsのデモアプリケーションで動かしてみる

Googleが提供しているオープンソースのツールで、ORMが生成するSQLの調査を容易にしてくれるらしいので使ってみました。
具体的にはログなどに、コメントが含まれるようになり、ORMが生成したSQL文はどのコードで生成されたものかを知ることができるみたいです。

今回Rails APIモードのデモアプリケーションが用意されているので、どのようなコメントが出力されるか動かして確認してみます。
google/sqlcommenter | GitHub

sqlcommenterが対応する言語、フレームワーク、DBは以下

言語

  • Ruby
  • Python
  • JavaScript
  • Node.js
  • Java

フレームワーク

  • Ruby on Rails
  • Flask
  • Django
  • Express
  • Knex.js
  • etc...etc

DB

  • MySQL
  • MariaDB
  • PostgreSQL
  • SQLite
  • Google Cloud SQL

デモRailsアプリケーションで試してみる

google/sqlcommenter | Rails demo

RailsのAPIデモアプリケーションでとりあえずsqlcommenterがどんなものかを体験ができるみたいです。
ただ、sqlcommenter_railsはrubygemsではリリースされていないようなので、上記のREADMEの手順に沿って環境構築すればとりあえずは使うことができます。

APIは以下の2つが用意されています。

  • Get /posts
  • POST /posts

APIを叩くcurl

bash
curl localhost:3000/posts
bash
curl -X POST localhost:3000/posts -d 'title=my-post'

POST /postsを実行

bash
curl -X POST localhost:3000/posts -d 'title=my-post'
INSERT INTO "posts" ("title", "created_at", "updated_at") VALUES (?, ?, ?)
 /*action='create',application='SqlcommenterRailsDemo',controller='posts',db_driver='ActiveRecord::ConnectionAdapters::SQLite3Adapter',
 framework='rails_v6.0.3.5',route='/posts', traceparent='00-e71c784d7151a939ef4e8d886a326456-82ad00a116eaa038-01'*/

初期設定では以下の情報が出力されるようです。

  • action
  • application
  • controller
  • db_driver
  • framework
  • route
  • traceparent

設定を追加することで、出力する情報の変更 / 追加が可能です。

bash
mkdir config/initializers
touch config/initializers/marginalia.rb
vim config/initializers/marginalia.rb

# 追加
Marginalia::Comment.components = [ :action, :application, :controller_with_namespace, :hostname, :job, :line]
~
~
~

Rails serverを再起動して再度curlを実行すると先ほど追加した情報が表示されます

/*action='create',application='SqlcommenterRailsDemo',controller_with_namespace='PostsController',
hostname='xxxx',line='/app/controllers/posts_controller.rb:25:in `create\''*/

image.png

GET /postsを実行

次にpostの一覧を取得してみます。

bash
curl localhost:3000/posts
SELECT "posts".* FROM "posts"
/*action='index',application='SqlcommenterRailsDemo',controller_with_namespace='PostsController',
hostname='xxxx',line='/app/controllers/posts_controller.rb:19:in `index\''*/

先程のPOSTと同じくactionや実際にSQL文が生成されたコードの位置を出力してくれました。

posts_controller.rbは以下のようになっています。

app/controllers/posts_controller.rb
#...
17 class PostsController < ApplicationController
18   def index
19    render json: Post.all
20   end
21
22   def create
23     title = params[:title].to_s.strip
24     head :bad_request if title.empty?
25     render json: Post.create!(title: title)
26   end
27  end

参考

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

N+1問題の発見方法と解決方法(備忘録)

概要

N+1問題とは必要以上にSQL文が発行されてパフォーマンスが低下する問題です。
N+1問題の解決方法
便利なgem bullet(N+1を検出してくれる)の導入
最後に

初学者のただの備忘録です。

環境

  • Ruby: 2.6.6
  • Rails: 6.0.3.5

実装例

例えば、パラメータのcategory_nameがDBに存在するかどうかによって
@ideasに代入される値が変わる処理があるとします。

app/controllers/ideas_controller.rb
def index
  category = Category.find_by(name: params[:category_name])
  if category.present?
    @ideas = category.ideas
    render formats: :json
  else
    @ideas = Idea.all
    render formats: :json
  end
end

categoryとideaの関係は

app/models/category.rb
has_many :ideas, dependent: :destroy
app/models/idea.rb
belongs_to :category

N+1問題が発生してない場合

category.present?がtrueの場合は@ideasにcategory.ideasが代入されて
レンダリングされる。
そのときのログを一応確認(わかりやすいように一部ログをはしょっています)
CategoryとIdeaが1回ずつ読み込まれてます。N+1問題は発生していません。

ターミナル
  Category Load (0.5ms)  SELECT `categories`.* FROM `categories` WHERE `categories`.`name` = 'hoge1' LIMIT 1
  ↳ app/controllers/api/v1/ideas_controller.rb:13:in `index'
  Idea Load (0.4ms)  SELECT `ideas`.* FROM `ideas` WHERE `ideas`.`category_id` = 1
  ↳ app/views/api/v1/ideas/index.json.jbuilder:2

N+1問題が発生している場合

category.present?がfalseの場合、@ideasにIdea.allが代入されてレンダリングされる。
ログを確認するとCategory Loadが何回も...
これがN+1問題です。

ターミナル
  Idea Load (0.5ms)  SELECT `ideas`.* FROM `ideas`
   app/views/api/v1/ideas/index.json.jbuilder:2
  Category Load (0.3ms)
   app/models/idea.rb:4:in category_name
  Category Load (0.3ms)
   app/models/idea.rb:4:in category_name
  CACHE Category Load (0.0ms)
   app/models/idea.rb:4:in category_name
  Category Load (0.3ms) 
   app/models/idea.rb:4:in category_name
  Category Load (0.4ms)
   app/models/idea.rb:4:in category_name
  Category Load (0.3ms)
   app/models/idea.rb:4:in category_name
  CACHE Category Load (0.0ms) 
   app/models/idea.rb:4:in category_name
  Category Load (0.4ms)
   app/models/idea.rb:4:in category_name
  CACHE Category Load (0.0ms)
   app/models/idea.rb:4:in category_name
  Category Load (0.4ms)
   app/models/idea.rb:4:in category_name

N+1問題を解決する方法

結論から言いますとincludes、preload、eager_loadのいずれかを使用すると解決できます。

他の記事ではよくincludesで解決してますが、結局のところincludesは条件によって
preloadしたりeager_loadしたりする。preloadはエラー(例外)を発生させることもあるので
eager_loadを使用するのが無難だと思います。

詳しく知りたい方は
下記の記事を読んでみてください。

ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い

では、解決していきます。

eager_load (LEFT OUTER JOIN)の場合

app/controllers/ideas_controller.rb
# def index
#   category = Category.find_by(name: params[:category_name])
#   if category.present?
#     @ideas = category.ideas
#     render formats: :json
#   else
      @ideas = Idea.eager_load(:category)  # もとはIdea.all
#     render formats: :json
#   end
# end
ターミナル
  Category Load (0.6ms)  SELECT `categories`.* FROM `categories` WHERE `categories`.`name` = 'hoge1ddd' LIMIT 1
  ↳ app/controllers/api/v1/ideas_controller.rb:13:in `index'
  Rendering api/v1/ideas/index.json.jbuilder
  SQL (0.5ms)  SELECT `ideas`.`id` AS t0_r0, `ideas`.`category_id` AS t0_r1, `ideas`.`body` AS t0_r2, `ideas`.`created_at` AS t0_r3, `ideas`.`updated_at` AS t0_r4, `categories`.`id` AS t1_r0, `categories`.`name` AS t1_r1, `categories`.`created_at` AS t1_r2, `categories`.`updated_at` AS t1_r3 FROM `ideas` LEFT OUTER JOIN `categories` ON `categories`.`id` = `ideas`.`category_id`
  ↳ app/views/api/v1/ideas/index.json.jbuilder:2

何回もCategoryがLoadされていたSQL文が発行されなくなりました。

preloadの場合

app/controllers/ideas_controller.rb
# def index
#   category = Category.find_by(name: params[:category_name])
#   if category.present?
#     @ideas = category.ideas
#     render formats: :json
#   else
      @ideas = Idea.preload(:category)  # もとはIdea.all
#     render formats: :json
#   end
# end
ターミナル
  Category Load (0.4ms)  SELECT `categories`.* FROM `categories` WHERE `categories`.`name` = 'hoge1ddd' LIMIT 1
  ↳ app/controllers/api/v1/ideas_controller.rb:13:in `index'
  Rendering api/v1/ideas/index.json.jbuilder
  Idea Load (0.5ms)  SELECT `ideas`.* FROM `ideas`
   app/views/api/v1/ideas/index.json.jbuilder:2
  Category Load (0.4ms)  SELECT `categories`.* FROM `categories` WHERE `categories`.`id` IN (1, 2, 3, 4, 5, 6, 7)
  ↳ app/views/api/v1/ideas/index.json.jbuilder:2

includesの場合

app/controllers/ideas_controller.rb
# def index
#   category = Category.find_by(name: params[:category_name])
#   if category.present?
#     @ideas = category.ideas
#     render formats: :json
#   else
      @ideas = Idea.includes(:category)  # もとはIdea.all
#     render formats: :json
#   end
# end
ターミナル
  Category Load (0.3ms)  SELECT `categories`.* FROM `categories` WHERE `categories`.`name` = 'hoge1ddd' LIMIT 1
  ↳ app/controllers/api/v1/ideas_controller.rb:13:in `index'
  Rendering api/v1/ideas/index.json.jbuilder
  Idea Load (0.4ms)  SELECT `ideas`.* FROM `ideas`
   app/views/api/v1/ideas/index.json.jbuilder:2
  Category Load (0.2ms)  SELECT `categories`.* FROM `categories` WHERE `categories`.`id` IN (1, 2, 3, 4, 5, 6, 7)
  ↳ app/views/api/v1/ideas/index.json.jbuilder:2

おまけ

Ideaモデルに複数の関連付けがある場合は下記のように書くことができます。

app/controllers/ideas_controller.rb
# def index
#   category = Category.find_by(name: params[:category_name])
#   if category.present?
#     @ideas = category.ideas
#     render formats: :json
#   else
      @ideas = Idea.eager_load(:category, :hoge, :huga)
#     render formats: :json
#   end
# end

gem "bullet"の導入方法

N+1問題を検出してくれるgemです。
通常はターミナルに出ますが、jquery等でカスタムもできます。
今回それは割愛

Gemfile
gem "bullet"

bundleだけでも可能

ターミナル
$ bundle install

yでconfig/environments/development.rbを自動作成

ターミナル
$ bundle exec rails g bullet:install
  Would you like to enable bullet in test environment? (y/n) 

N+1問題が発生するとターミナルに下記のようにでます。(一部省略)
IdeasテーブルにCategoriesテーブルをincludesしてねということ

ターミナル
user: shu
GET /api/v1/ideas?category_name=
USE eager loading detected
  Idea => [:category]
  Add to your query: .includes([:category])

以上。

間違っている等ありましたらご指摘頂けると幸いです。

最後に

N+1問題の解決方法自体はそんなに難しいコードを書くわけではないですがSQLを読んで
なにが起こっているのかしっかり理解することが大切だと思います。
(解決できたらいいやーでは逆に遠回りになることを身を持って感じております。)

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

【Rails】 フラッシュメッセージ使い分け

はじめに

お恥ずかしながら、私は最近までフラッシュメッセージってflash[:notice] = ⚪︎⚪︎と書いておけばいいんだろうくらいの認識しかありませんでした。ポートフォリオ作成時にフラッシュメッセージの使い分けの方法があることを知り、少し理解を深めることができたので、自身の備忘録として残しておきたいと思います。

フラッシュメッセージって何?

何かの処理を行った後に画面に表示されるメッセージのことを指します。
例) ● ログイン時
    「ログインに成功しました」
   ● 記事の投稿時
    「記事の投稿に成功しました」

見本

スクリーンショット 2021-02-20 1.14.25.png

フラッシュメッセージの種類

● flash

次のリクエストまで表示させることができる。
= redirect_toの際に使用する。
※render時に使用すると次のリクエスト時までメッセージが画面に表示され続けてしまうので、不適切になります。

● flash.now

現在のリクエストまでしか表示できない。
= renderの際に使用する。


● notice

通知を出す時。主に成功した時のメッセージなど。

● alert

警告の通知を出す時。何かの動作に失敗時など。

コントローラー

今回は、投稿した際の保存成功時と失敗時にメッセージが表示されるようにしています。

books_contoroller.rb
def create
 @book = Book.new(book_params)
 @book.user_id = current_user.id
 if @book.save
  redirect_to books_path, notice: = "投稿に成功しました"
 else
  flash.now[:alert] = "投稿に失敗しました"
  render :new
 end
end

# 2行に分けて記述することも可能
# redirect_to books_path
# flash[:notice] = "投稿に成功しました"

ビュー

viewにもフラッシュメッセージが表示されるように記述を加えます。
※フラッシュメッセージを表示させるタイミングは何度もあるかと思いますので、部分テンプレートに書き出し、呼び出す形にしています。

books/index.html.erb
<%= render 'layout/flash' %>

<% @books.each do |book| %>
 <%= book.user.name %>
 <%= book.title %>
 <%= book.content %>
<% end %>

layout/_flash.html.erb
<% if notice %>
 <%= notice %>
<% elsif alert %>
 <%= alert %>
<% end %>

終わり

今回は以上になります。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になれば幸いです。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。

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

[Ruby on Rails] CSSが反応してくれない時の対処法

自身の備忘録を書いて行きます。

  
CSSを書いても全く反応してくれないことがありました。
なんでや?と思い調べて行くと、ちゃんと動いてくれるようになりました。

それの解決策を書いて行きます。

解決策

layouts/application.html.erb
 <%= stylesheet_link_tag 'application', media: 'all'%>

上記の記述をすることでCSSを読みこむようになりました。
こちらはCSSの類を全て読みこんでくれる記述らしいです。

  

その他に
ユーザー管理機能(devise使用の場合)の
ログイン等の配置を動かす場合は

config/initializers/devise.rb
config.scoped_views = true

上記の記述は元々、247行目でコメントアウトになっている
config.scoped_views = false
こういった記述になっている。

それをコメントアウトを外してtrueにすればいいらしい。

  

実際、両方やってたら問題なく動いているので、
今の所は大丈夫そう。

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

Rails ERDのススメ

About Rails ERD?

Railsのテーブル情報を自動生成するためのgem
https://github.com/voormedia/rails-erd
ER図をpdfで出力することができる。

こんな感じ⬇️
image.png

Getting started ?

for mac

brew intall graphviz

gemfile
gem install ‘rails-erd’, group: :development
  • 設定でmigrationの度に自動生成することも可能

Pros and cons

?

クライアント・新入メンバーへの説明するのに便利

?

関連にもよるがテーブル数が50超えてくるとごちゃごちゃしてすごく見づらい

内部の開発に関しては、モデル数20以下の場合annotateで十分
ある程度の規模のサービスの場合、LucidChartやdraw.ioで作図するするのがオススメ

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

Docker,CircleCI,AWS,Railsでポートフォリオを作成

前書き

就職活動用のポートフォリオをDocker,CircleCI,AWS(ECS)を用いてWebアプリケーションを作成しました。
バックエンドにRuby on Railsを用いました。
今回は作成したポートフォリオの機能、参考記事などを紹介したいと思います。

概要と作成背景

今回作成したアプリはスイーツのデリバリーに特化したECサイトです。

  • ケーキ屋のアルバイト経験からお店の業務改善をしたい
  • 手軽にお店のスイーツを食べたい
  • 店舗で販売しているようなスイーツを配達するサービスが存在しない(自分調べ)

上記の理由により飲食店にもユーザーにもメリットのあるサービスを作成しようと思いました。

アプリのURL: http://sweetsdeli.com

GitHubのURL: https://github.com/sekine617/sweetsdeli

画面イメージ紹介

トップページ

新着商品、人気商品などを表示
人気商品はユーザーの購入履歴から購入数が多い商品を表示しています。
スクリーンショット 2021-02-19 15.07.58.png

商品一覧ページ

こちらではカテゴリー検索、商品名検索などができます。
スクリーンショット 2021-02-19 15.27.36.png

商品詳細ページ

こちらでは商品をカートに入れる、お気に入り登録、商品のレビュー投稿などができます。
スクリーンショット 2021-02-19 15.27.58.png

マイページ

マイページではプロフィール編集、お気に入り、購入履歴、投稿したレビューを閲覧できます。

スクリーンショット 2021-02-19 15.21.47.png

スクリーンショット 2021-02-19 15.25.44.png

スクリーンショット 2021-02-19 15.25.57.png

スクリーンショット 2021-02-19 15.30.25.png

ショッピングカート

こちらではカートに入れた商品の確認、個数の変更、カートから削除などができます。
スクリーンショット 2021-02-19 15.29.35.png

購入画面

カートに入れた商品から合計金額を確認し、クレジットカードによる決済が可能です。実際に決済する場合はpay.jpのてすとカードを使用してください。
スクリーンショット 2021-02-19 15.30.02.png
このようにカード情報の入力フォームがモーダルウィンドウで出てきます。
スクリーンショット 2021-02-19 15.46.47.png

使用言語

  • フロントエンド
    • jQuery
    • HTML
    • CSS/Sass
    • Bootstrap
  • バックエンド
    • Ruby on Rails
    • Ruby
    • PAY.JP(外部API)
  • インフラ
    • Docker/docker-compose
    • CircleCI
    • nginx
    • mysql
    • AWS(ECS, ALB, S3, RDS, Route53, ECR, VPC, IAM)

インフラ構成図

スクリーンショット 2021-02-19 18.41.03.png

開発環境

機能一覧

  • ユーザー関連(devise)
    • 登録機能
    • プロフィール編集機能
    • ログイン・ログアウト機能
  • 決済機能(PAY.JP API)
  • 人気商品表示機能
  • タグ機能
  • 商品登録機能
  • 画像アップロード機能(AWS S3バケット, carrierwave)
  • お気に入り機能(非同期処理, jQuery)
  • フラッシュメッセージ表示機能
  • レビュー投稿機能
  • カート関連
    • カート登録機能
    • カート編集機能
  • 住所自動入力機能(jQuery)
  • 商品名検索機能
  • カテゴリー検索機能
  • ページネーション機能(kaminari)

データベース設計

スクリーンショット 2021-02-19 21.28.45.png

テーブル説明

テーブル名 説明
users ユーザー情報
orders 注文管理(受け取り日時など)
orders_products  注文商品管理
products 商品情報
reviews 商品に対するレビューを管理
likes 商品へのお気に入り情報
address ユーザーの住所
cart_items cartsとproductsの中間テーブル
shops ショップ情報
carts カート追加した商品情報一時的に保存
product_tag_relations productsとtagsの中間テーブル
tags 商品のカテゴリ情報

ポイントはproductsテーブルのquantity_per_dayで、1日の提供数を指定し注文数が1日の提供数を上回らないようにしました。
また注文時に受け取り日時を指定し、各店舗ごと、日にちごとに管理が可能です。

苦労した点

CircleCIでAWSへの自動デプロイ

CircleCIを用いて開発環境のDockerイメージをECRにpushし、ECSのタスク定義を更新してデプロイをしましたが、config.ymlの設定でのエラーに悩まされました。
またAWSの各設定にもかなり悩まされ、インフラの知識がないことで時間が結構かかりました。

credentials.ymlでのpay.jpなどのアクセスキー管理でなかなかうまく行かずインデントの重要性がよくわかりました。

参考記事

Railsでのテストを参考にしました。
【Rails】はじめてのRSpec!テストコードを書こう!

deviseのログイン機能を参考にしました
【Rails】ログイン機能を実装する

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

git push -u origin mainでpushされずerrorになる

rails tutorialのversion 6.0の1章でgitの扱い方を学びながら進めていたがgit push -u origin mainをしたら下記のようなエラーに遭遇した

terminal

error: src refspec main does not match any
error: failed to push some refs to '自分のgithubアカウント'

原因

2020/10月からgithubはレポジトリ名をmasterからmainに変更したためmainでpushしてもレポジトリ名が違うためerrorになるとのこと

次のようにgit branchで調べるとその通りだった

% git branch
* master

ので次のようにブランチを変えて解決

% git branch -M main
% git branch
* main

まだまだ初学者なのだと実感させられる

参考になったURL

https://deepblue-ts.co.jp/tips/git-push-error/

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