学習記録

アウトプット用に作りました

PFのテーマカラーを選択できるようにする

PFのテーマカラーがデフォルトのピンクとブルー系の2つからユーザーが選択して設定できるようにしたいです。

進めていく手順

  1. Userテーブルにcolorカラムを追加する
  2. ユーザー新規登録ページとプロフィール編集ページにフォームを追加
  3. コントローラのストロングパラメータにcolor追加
  4. cssファイルを編集

まず最初にUsersテーブルにcolorカラムを追加しました。

$ bundle exec rails g migration AddColorToUsers color:integer
Running via Spring preloader in process 70954
      invoke  active_record
      create    db/migrate/20210714133754_add_color_to_users.rb

作成されたマイグレーションファイルにデフォルト値を追加します。

class AddColorToUsers < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :color, :integer, default: 0
  end
end

マイグレートしてデータベースに追加しました。

$ bundle exec rails db:migrate
== 20210714133754 AddColorToUsers: migrating ==================================
-- add_column(:users, :color, :integer, {:default=>0})
   -> 0.7259s
== 20210714133754 AddColorToUsers: migrated (0.7260s) =========================

そしてuserモデルにcolorカラムについての記述を行います。

class User < ApplicationRecord
# サイトのテーマカラー
  enum color: { pink: 0, blue: 1}
end

pinkをデフォルトにします。

次にフォームをビューに追加していこうと思います。simple_formを使用しています。最初にユーザー新規作成画面

selectedでフォームの初期値を入れています。

users/new.html.erb

<div class="form-group">
    <%= f.input :color, as: :select, selected: 'pink', collection: User.colors.keys, class: "form-control", hint: "マイページのテーマカラーを選択してください" %>
</div>

collection:でセレクトフォームで選べる項目を入れています。Userのcolor項目のハッシュからキーを配列で取得しています。実際にコンソールで確認しながら作成しました。

Sayo-MacBook-Pro:emoji_diary SAYO$ rails c
Running via Spring preloader in process 74168
Loading development environment (Rails 6.1.3.2)
irb: warn: can't alias context from irb_context.
irb(main):001:0> User.colors
=> {"pink"=>0, "blue"=>1}
irb(main):002:0> User.create(nickname: '👓', name: "megane", password: 'password', password_confirmation: 'password', email: 'megane@example.com', color: 'blue')
  TRANSACTION (0.2ms)  BEGIN
  User Exists? (0.4ms)  SELECT 1 AS one FROM `users` WHERE `users`.`email` = 'megane@example.com' LIMIT 1
  User Exists? (39.1ms)  SELECT 1 AS one FROM `users` WHERE `users`.`name` = 'megane' LIMIT 1
  User Create (88.9ms)  INSERT INTO `users` (`nickname`, `name`, `crypted_password`, `salt`, `created_at`, `updated_at`, `email`, `color`) VALUES ('👓', 'megane', '$2a$10$AhqCyQAETZxQEjUZtl7qb.alfkxg.KS3zSgj5rW3s1A8ulbTS.y.e', 'o4tKY5Vtr8RWcxNxt8Fw', '2021-07-14 15:34:14.588242', '2021-07-14 15:34:14.588242', 'megane@example.com', 1)
  TRANSACTION (7.1ms)  COMMIT
=> #<User id: 25, nickname: "👓", name: "megane", crypted_password: "$2a$10$AhqCyQAETZxQEjUZtl7qb.alfkxg.KS3zSgj5rW3s1A...", salt: "o4tKY5Vtr8RWcxNxt8Fw", created_at: "2021-07-15 00:34:14.588242000 +0900", updated_at: "2021-07-15 00:34:14.588242000 +0900", email: "megane@example.com", role: "general", color: "blue">
irb(main):005:0> user = User.last
  User Load (92.5ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
=> #<User id: 25, nickname: "👓", name: "megane", crypted_password: "$2a$10$AhqCyQAETZxQEjUZtl7qb.alfkxg.KS3zSgj5rW3s1A...", salt: "o4tKY5Vtr8RWcxNxt8Fw", created_at: "2021-07-...
irb(main):006:0> user.color
=> "blue"
irb(main):007:0> user = User.first
  User Load (29.2ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User id: 1, nickname: "🐒", name: "sayo", crypted_password: "$2a$10$idRbazbxLnZamo9L0JOzuOf7Dy40FD7lK/GCUMb6O7/...", salt: nil, created_at: "2021-06-30 15:30:20.862197000 ...
irb(main):008:0> user.color
=> "pink"

次にプロフィール編集ページのフォームに追加します。

<div class="form-group">
    <%= f.input :color, as: :select, include_blank: false, collection: User.colors.keys, class: "form-control", hint: "マイページのテーマカラーを選択してください" %>
</div>

include_blank: falseで空白で投稿できないようにします。

そしてフォームを追加したのでストロングパラメーターにcolorを追加して、データを受け取れるようにします。

class UsersController < ApplicationController
    def user_params
    params.require(:user).permit(:nickname, :name, :password, :password_confirmation, :email, :color)
  end
end
class ProfilesController < ApplicationController
    def user_params
    params.require(:user).permit(:nickname, :name, :password, :password_confirmation, :email, :color)
  end
end

cssファイルにblueのテーマカラーを追加していきます。私が考えた実装の仕方なのでとても冗長的で汚いコードになっていると思うし、こんなこと絶対にしない!って思われるかもしれないです。でも一応思ったように動いたので、これで良しとしました。

やったこと

  • cssファイルで色を変えたい記述にcss変数を使う
  • application.html.erbでif文を使って切り替える

まず最初にapplication.scssファイルにデフォルトのpinkの色をcss変数を使って定義していきます。

:root {
  --color1: #fbe6de;
  --color2: #c76e54;
  --color3: #f5cdbd;
  --color4: #bf9288;
  --color5: #f5ece4;
}

色をつける部分で下記のように指定します。全て変更してサイトの色が変わらず出力されているか確認しました。

#header {
  background-color: var(--color1);
}

次に青色のテーマを管理するapplication_blue.scssファイルを作成しました。これをapplication.scssファイルの代わりに使いたいので、必要なcssファイルや、bootstrapの記述も書き写しました。

@import 'home';
@import 'simple_calendar';
@import 'diaries.scss';
@import 'users.scss';
@import 'profiles.scss';
@import 'header_footer.scss';
@import '~bootstrap/scss/bootstrap';
@import '~@fortawesome/fontawesome-free/scss/fontawesome';

.blue {
  --color1: #ABBDDA;
  --color2: #399ECC;
  --color3: #7097C3;
  --color4: #444f7c;
  --color5: #E0ECFF;
}

そしてapplication.html.erbファイルで条件分岐を使って、colorがpinkとログインしていない時のcssファイルでapplication.scssファイルを使うこと、そしてそれ以外ではapplicaction_blue.scssファイルを使用することを定義していきます。

<!DOCTYPE html>
<% if !logged_in? || (current_user.color == "pink") %>
<html>
  <head>
    <title><%= page_title(yield(:title)) %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_pack_tag 'application', media: 'all' %>
    <%= javascript_pack_tag 'application' %>

    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
  </head>

  <body>
    <% if logged_in? %>
      <%= render 'shared/header' %>
    <% else %>
      <%= render 'shared/before_login_header' %>
    <% end %>
    <%= render 'shared/flash_message' %>
    <%= yield %>
    <%= render 'shared/footer' %>
  </body>
</html>

<% else %>

<html class="blue">
  <head>
    <title><%= page_title(yield(:title)) %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_pack_tag 'application_blue', media: 'all' %>

    <%= javascript_pack_tag 'application' %>

    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
  </head>

  <body>
    <% if logged_in? %>
      <%= render 'shared/header' %>
    <% else %>
      <%= render 'shared/before_login_header' %>
    <% end %>
    <%= render 'shared/flash_message' %>
    <%= yield %>
    <%= render 'shared/footer' %>
  </body>
</html>
<% end %>

タグから切り替えるようにしているのでとても長いコードになってしまいました。最初はstylesheet_pack_tagのコードを下記のように条件分岐した感じで書いていたのですがcssファイルの:rootと書いている部分の記述でうまくいかなかったのでこのようになりました。

blueに変更するときは<html class="blue">と書いてクラスを当てています。

ローカル環境ではこの記述で問題なくテーマカラーを変更することができました。しかしherokuにpushして本番環境で動作するか確認すると500エラーが出てしましました。エラー文を残していなくてとても後悔しています。。唯一検索履歴に残っていたWebpacker can't find application_blue.cssこれがエラーの部分です。webpackerがapplication_blue.cssファイルを見つけられない?といっています。調べていくと同じようなエラーが出ている人を見つけました。

https://github.com/rails/webpacker/issues/2071

config/webpacker.ymlの中でextract_css: trueの場合にはlinkを出力し、extract_css: falseの場合にはJavaScriptを使って動的にCSSを読み込むという動作になります。development環境ではfalseになっていたのでproduction環境でもfalseに設定しました。

production:
  <<: *default

  # Production depends on precompilation of packs prior to booting for performance.
  compile: false

  # Extract and emit a css file
  extract_css: false

  # Cache manifest.json for performance
  cache_manifest: true

詳しい説明が下記に載っていました。

https://zenn.dev/ryouzi/articles/da8a77accc221e#stylesheet_pack_tagがproduction環境でエラーが出る