- 投稿日:2019-09-29T23:57:51+09:00
Rails 5.2 + Docker, RAILS_MASTER_KEYをイメージ作成時に動的に入れる方法
ブログから移動したものです。
下記のような形でDockerfileを作成する。
FROM ruby:2.6.0-alpine3.8 ENV RAILS_ENV=production ENV APP_ROOT /usr/src/app ARG RAILS_MASTER_KEY ENV RAILS_MASTER_KEY ${RAILS_MASTER_KEY} WORKDIR $APP_ROOT RUN apk add --no-cache alpine-sdk \ nodejs-current \ nodejs-npm \ yarn \ mysql-client \ mysql-dev \ python2 \ tzdata COPY Gemfile $APP_ROOT COPY Gemfile.lock $APP_ROOT RUN bundle install --jobs=4 COPY . $APP_ROOT RUN bin/yarn install RUN bin/rails webpacker:compile VOLUME $APP_ROOT/public VOLUME $APP_ROOT/tmp下記の部分がポイントとなっている。
ARG RAILS_MASTER_KEY ENV RAILS_MASTER_KEY ${RAILS_MASTER_KEY}下記のような形でCI上で環境変数をARGとして渡してやれば(CI側の環境変数にRAILS_MASTER_KEYが設定されている前提)、上記のARGにRAILS_MASTER_KEYが設定され、ENVのRAILS_MASTER_KEYにARGのRAILS_MASTER_KEYを設定することができる。これにより、イメージ作成時にRAILS_MASTER_KEYを設定した状態でビルドすることができる。
docker build --build-arg RAILS_MASTER_KEY=${RAILS_MASTER_KEY} -t app . -f Dockerfile.production参考:
* https://qiita.com/NaokiIshimura/items/2a179f2ab910992c4d39
* https://qiita.com/nacika_ins/items/cf8ceb20711bd077f770
- 投稿日:2019-09-29T23:56:35+09:00
【Rails】kaminariで作ったページネーションの1ページあたりの表示件数を変更する方法
kaminariで作ったページネーションは、デフォルトだと25件まで表示する設定になっています。
1ページあたりの表示件数を変更するには、config/initializers/kaminari_config.rbを編集すればOK。
今回は1ページあたり10件の表示に変更することにしましょう。
config/initializers/kaminari_config.rbを開くと、コメントアウトされている項目がたくさんあると思います。
表示件数を変える場合は、1番上の# config.default_per_page = 25の#を取って、以下のように変更してください。
config/initializers/kaminari_config.rbconfig.default_per_page = 10変更したら、rails sでサーバーを再起動してください。
表示件数が10件になっているはずです。
ちなみに、他の項目は以下のような意味です。
【default_per_page】
- 1ページあたりの表示件数
【max_per_page】
- 1ページあたりの表示件数
- デフォルトはnil = 無制限
【window】
- 左右何ページ分のリンクを表示するかの設定
- 3に設定して現在10ページ目にいる場合、7・8・9ページと11・12・13ページが表示される
【outer_window】
- 最初のページ・最終ページからそれぞれ何ページ分のリンクを表示するかの設定
- 3と設定して全20ページの場合、1・2・3ページおよび18・19・20ページが表示される
【left】
- 先頭ページから何ページ分のリンクを表示するかの設定
【right】
- 最終ページから何ページ分のリンクを表示するかの設定
【page_method_name】
- ページ番号を指定するスコープの名前
【param_name】
- ページ番号を渡すのに使うリクエストパラメータの名前
以上です。
以上です。
- 投稿日:2019-09-29T23:47:11+09:00
【Rails】kaminariのページネーションを中央寄せする方法
application.scssに、以下のように記述しましょう。
これでページネーションが中央寄せになります。
application.scss.pagination { justify-content: center; }kaminariでは、ul要素に「.pagination」というclassが自動で付与されます。
そのため、.paginationクラスのcssを変更すればOKです。
- 投稿日:2019-09-29T23:45:12+09:00
【Rails】kaminariのページネーションを日本語化する方法
kaminariを日本語化する方法
kaminariを日本語化する方法はたったの2ステップです。
ステップ1:ステップ1:config/application.rbを編集
config/application.rbを開いて、以下のコードを書いてください。
config/application.rbconfig.i18n.default_locale = :jaステップ2:config/lacales/ja.ymlを作成
次にconfig/lacales/ja.ymlといファイルを作りましょう。
ファイルを作ったら、以下のように記述します。
config/lacales/ja.ymlja: views: pagination: first: '最初' last: '最後' previous: '前' next: '次' truncate: '...'記述したら、rails sでサーバーを再起動してください。
以下のように、日本語になっていたら成功です。
- 投稿日:2019-09-29T23:40:40+09:00
【Rails】kaminariを使ってページネーションを実装する方法
ページネーションを作るのに、kaminariというgemを使ったのですが久しぶりで忘れてしまっていました。
そこで備忘録として
- kaminariの導入方法
- kaminariのカスタマイズ方法
についてまとめてみました。
よかったら参考にしてみてください。
【Rails】kaminariを使ってページネーションを実装する方法
kaminariによるページネーションの実装は、大きく分けて6つのステップでできます。
ステップ1:Gemgileに記述
Gemfileに以下のように記述してください。
gem 'kaminari'ステップ2:bundle installする
Gemfileにkaminariを記述したら、ターミナルを開いてbundle installしてください。
ステップ3:kaminariの設定ファイルを生成する
次にkanimariの設定ファイルを作ります。
以下のコマンドをターミナルに打ち込んでください。
rails g kaminari:config以下のように表示されたら成功です。
ステップ4:kaminariのviewファイルの生成
次にviewファイルを作ります。
今回はbootstrap4で見た目を整えるので、以下のようなコマンドを打ちました。rails g kaminari:views bootstrap4以下のように表示されたら成功です。
ちなみにBootstrap4を使わない場合は、以下のように打ち込んでください。
rails g kaminari:views defaultステップ5:controllerにpageメソッドを追加
ページネーションを実装したいアクションを持つcontrollerにpageメソッドを追加しましょう。
.page(params[:page])例えば、indexアクションに表示される記事一覧(@articles)にページネーションを使いたい場合は、こんな感じで書けばOK。
pageメソッドを呼び出すことにより、引数に指定したページに表示するデータだけを取得できます。
デフォルトの設定では、1ページに25件のデータが取得されます。
ステップ6:viewファイルの修正
次にviewファイルを修正していきましょう。
ページネーションを表示したい箇所に以下のコードを書いてください。
<%= paginate @articles %>以下のように表示されたら成功です。
まとめ
kaminari便利!
- 投稿日:2019-09-29T23:35:50+09:00
【初心者向け】コメント機能作成追加
前提
・記事投稿一覧ページがすでに作成されている状態であること
・記事投稿一覧ページにコメント機能を追加する
・コメントはUserと紐づいてはいない仕様にしています
(説明が少なくて恐縮ですがお許しください)コメント機能作成
コメントモデルを作成する
作成するカラムは
user_id
content
topic_id
を用意します。$ rails g model comment user_id:integer content:text topic_id:integerrails db:migrateアソシエーションの追加(モデルの関連付け)
コメントと投稿の関係は以下のようになります。
・Commentは1つのTopicを持っている
・Commentは1つのUserを持っている
・Topicは複数のCommentを持っている
という関連を持っています。ですので、Commentモデルに「belongs_to」、Topicモデルに「has_many」を以下のように追加します。
app/models/comment.rbclass Comment < ApplicationRecord belongs_to :topic belongs_to :user validates :content, presence: true endmodels/topic.rbhas_many :comments, dependent: :destroy has_many :comment_users, through: :comments, source: 'user'コントローラを作成しルーティングを設定する
$rails g controller commentsconfig/routes.rbget 'comment/new' #中略 resources :topics do resources :comments #/topics/:topic_id/comment/newのパスが使用できる endcontrollers/comments_controller.rbclass CommentsController < ApplicationController def new @comment = Comment.new @topic_id = params[:topic_id] end def create #コメントを登録する @comment = Comment.new #コメントのインスタンスを作成 @comment.topic_id = params[:topic_id] #記事番号をパラメータから受け取る @comment.content = params[:content] #コメントの内容をパラメータから受け取る if @comment.save #コメント登録の条件分岐 redirect_to topics_path, success: 'コメントに成功しました' else flash.now[:danger] = "コメントに失敗しました" render :new end end endリンクの追加
今回は投稿一覧ページの投稿からコメントページへと遷移できるようにします。コメントアイコンを用意してリンクを貼ります。
topics/index.html.erb<%= link_to new_topic_comment_path(topic_id: topic.id), method: :get do %> <%= image_tag 'icons/comment.png', class: 'topic-index-icon' %> <% end %>コメント投稿のページ作成
views/comments/new.html.erb<div class="topic-new-wrapper" > <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <h1 class="text-center">Add Comment</h1> <%= form_with url: '/topics/' + @topic_id.to_s + '/comments' ,local: true do |f| %> <div class="form-group"> <%= f.label :content %> <%= f.text_area :content, class: 'form-control' %> </div> <% if logged_in? %> <%= f.submit 'コメント送信', class: 'btn btn-black btn-block' %> <% end %> <% end %> </div> </div> </div> </div>作成結果
拙い記事で恐縮ですが、何かの参考になればと思います。
間違いや認識違い等ございましたらご指摘くださればと思います。
- 投稿日:2019-09-29T20:43:32+09:00
Ruby on Rails ツイートにユーザー情報を追加する。Active Recordによるアソシエーションとn + 1問題の解消
ツイートにユーザー情報を追加する
作業内容
- tweetsテーブルにカラムを追加する
- ツイート保存時にユーザー情報を追加する処理を記述する
- tweetsテーブルにカラムを追加
- tweetsテーブルに
user_id
カラムをinteger
型で追加する。ターミナル$ rails g migration AddUserIdToTweets user_id:integer # tweetsテーブルにuser_idカラムをinteger型で追加するマイグレーションファイルの作成 $ rake db:migrate # マイグレーションファイルの実行1. ツイート保存時にユーザー情報を追加する処理をする
●
current_user
deviseでログイン機能を実装すると、current_user
というヘルパーメソッドを使用することができる。これは、現在ログイン中のユーザーのレコードを、userクラスのインスタンスとして取得することができるメソッドである。
例を上げるとusersテーブル
でのid
カラムの値が1
のアカウントでログインしている場合、current_user
はUser.find(1)
と同じ意味を持つ。
この時、以下の例のようにcurrent_user.usersテーブルのカラム名
とすることで、ログイン中のユーザーの情報として登録されている各カラムの値を取得することができる。【例】
# current_userを利用して、ログインしているユーザーのデータを取得する current_user.name => "nakamura" current_user.email => "hoge@sample.com"
tweetsテーブルのuser_idカラムに保存すべきなのはcurrent_userのidカラムの値
になる。- ツイートを保存する際に、
image
,text
というビューから送られてくる情報に加えて、user_id
カラムにログイン中のユーザーのidを保存しなければならない。tweets_controller.rbclass TweetsController < ApplicationController before_action :move_to_index, except: :index def index @tweets = Tweet.page(params[:page]).per(5).order("created_at DESC") end def new end def create # tweet_paramsというストロングパラメーターを使用 Tweet.create(image: tweet_params[:image], text: tweet_params[:text], user_id: current_user.id) private def tweet_params params.permit(:image, :text) end def move_to_index redirect_to action: :index unless user_signed_in? end end
- createアクションのuser_id: current_user.idは現在ログイン中のユーザーの
id
を保存するための記述になる。2. マイページの作成
- マイページのルーティングを記述
- コントローラとアクションを作成
- マイページ用のビューファイルを作成
routes.rbの編集
routes.rbRails.application.routes.draw do devise_for :users root 'tweets#index" get 'tweets' => 'tweets#index' get 'tweets/new' => 'tweets#new' post 'tweets' => 'tweets#create' get 'users/:id' => 'users#show' endコントローラーとアクションを作成
● whereメソッド
where
メソッドはActiveRecordメソッドのうちの一つであり、モデル.where(条件)
のように引数部分に条件を指定することで、テーブル内の条件に一致したレコードのインスタンスを配列型で取得できる。
また、where
メソッドを連続して記述することで複数の条件に一致したレコードも取得できる。
【例】コンソール[1] pry(main)> Tweet.where('id < 3') => [#<Tweet id: 1, image: "test1.jpg", text: "ナイス!", created_at: "2019-09-27 00:00:00", updated_at: "2019-09-27 00:00:00", user_id: 1>,#<Tweet id: 2, image: "test2.jpg", text: "Thank you!", created_at: "2014-12-07 00:00:00", updated_at: "2014-12-07 00:00:00", user_id: 2>] # idが3未満のtweetsテーブルのインスタンスを配列で取得 [2] pry(main)> Tweet.where('id < 3').where(user_id: 1) => [#<Tweet id: 1, image: "test1.jpg", text: "いい景色だ。", created_at: "2014-12-06 00:00:00", updated_at: "2014-12-06 00:00:00", user_id: 1>] # idが3未満かつuser_idが1のtweetsテーブルのインスタンスを配列で取得users_controllerの作成
ターミナル$ rails g controller usersusersコントローラーのshowアクションにマイページに表示したい情報を定義
users_controller.rbclass UsersController < ApplicationController def show # 現在ログインしているユーザーのニックネーム @nickname = current_user.nickname # 現在ログインしているユーザーが投稿したツイート @tweets = Tweet.where(user_id: current_user.id).page(params[:page]).per(5).order("created_at DESC") end endマイページ用のビューファイルを作成
/users/show.html.erb#省略 <header class="header"> <div class="header__bar row"> <h1 class="grid-6"><a href="/">Tweet</a></h1> <% if user_signed_in? %> <div class="user_nav grid-6"> <span><%= current_user.nickname %> <ul class="user__info"> <li> <a href="/users/<%= current_user.id %>">マイページ</a> <%= link_to "ログアウト", destroy_user_session_path, method: :delete %> </li> </ul> </span> <a class="post" href="/tweets/new">投稿する</a> </div> <% else %> <div class="grid-6"> <%= link_to "ログイン", new_user_session_path, :class => 'post' %> <%= link_to "新規登録", new_user_registration_path, :class => 'post' %> </div> <% end %> </div> </header> #省略次回からはaタグの記述はヘルパーを使うようにする。
アソシエーションの利用
- アソシエーションの定義
- アソシエーションの実装
アソシエーションを定義する
- モデルクラスにはhas_manyやbelogns_toなどの定義がされている。
- 所属する側のテーブルに所属するクラス名_idというカラムがある。(
user_id
というカラムをtweetsテーブルに追加してあるため)userの作成したtweetが複数個(
has_many :tweets
)/models/user.rbclass User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :tweets endtweetはいずれかのuserに属する(
belongs_to :user
)/models/tweet.rbclass Tweet < ApplicationRecord belongs_to :user end以上でアソシエーションの定義をしたことになる。
アソシエーションを実装
【例】 アソシエーションをしない場合
$ rails c [1] pry(main)> user = User.find(1) [2] pry(main)> Tweet.where(user_id: user.id) => [#<Tweet id: 1, image: "http://photo1.jpg", text: "ナイス!", created_at: "2014-12-06 09:00:00", updated_at: "2014-12-06 09:00:00", user_id: 1>, #<Tweet id: 1, image: "http://photo2.jpg", text: "beautiful!", created_at: "2014-12-06 10:00:00", updated_at: "2014-12-06 10:00:00", user_id: 1>]【例】 アソシエーションを使用
$ rails c [1] pry(main)> user = User.find(1) [2] pry(main)> user.tweets => [#<Tweet id: 1, image: "http://photo1.jpg", text: "ナイス!", created_at: "2014-12-06 09:00:00", updated_at: "2014-12-06 09:00:00", user_id: 1>, #<Tweet id: 1, image: "http://photo2.jpg", text: "beautiful!", created_at: "2014-12-06 10:00:00", updated_at: "2014-12-06 10:00:00", user_id: 1>]アソシエーションを利用した
@tweets
を定義するusers_controllerclass UsersController < ApplicationController def show @nickname = current_user.nickname @tweets = current_user.tweets.page(params[:page]).per(5).order("created_at DESC") end end上記のようにcurrent_user.tweetsと続ければ、現在ログインしているユーザーの投稿したツイート全てを取得できる。
ツイートからユーザー情報を先読み
● n+1問題
n+1問題
とは、データを呼び出す際に大量のSQLが発行されてしまう問題のことである。例にあげると tweetsのindexアクションで全ツイートを取得する一回に加えて、アソシエーションを利用してツイートの数だけユーザー情報をその度に呼び出してしまう。 よって、その状態だとツイート数+1回SQLが発行されてしまう。この状態のことをn+1
問題と言う。● includesメソッド
n+1問題
はincludesメソッドを使うことによって解消することができる。指定された関連モデルをまとめて取得することで、SQLの発行回数を減らすことができる。- 記述の仕方はinludes(:モデル名)のように引数で、関連モデルをシンボル型で指定する。
includesメソッドを追記
tweets_controller.rbclass TweetsController < ApplicationController def index @tweets = Tweet.includes(:user).page(params[:page]).per(5).order("created_at DESC") end #以下省略 endまとめ
久しぶりに上記関係の復習をやってみましたが以前はなんのこっちゃ?みたいな部分があったのですがゆっくり読み返したことによって理解できたのではないかと思っています。
やはりこうしたアウトプットは重要だと痛感しました。
説明不足で端折ってる部分もあるかと思いますが、自分の備忘録としてこの記事を残させていただきます。
- 投稿日:2019-09-29T19:01:50+09:00
[初心者]Docker+Rails+MySQLでの環境構築の記録
はじめに
cloud9やvagrant以外にも
Dockerを使って環境構築してみたいと思い、勉強をしてみました!
Docker環境構築をする上で行った事を振り返ってまとめています。Dockerについて
- ホストOSのカーネルを使用している為、軽量で起動が高速
- 複数人で開発する時に開発環境を統一しやすい
- そのまま本番サーバーにデプロイする事ができる
などのメリットがあると知り、
様々の方のqiita記事など参考に環境構築に取り組んでみました。環境
- Mac OS
- Ruby 2.6.3
- Rails 5.2.3
- MySQL 8.0
【1】作業ディレクトリの作成、移動
$ mkdir sample $ cd sample【2】Dockerfileの作成
$ vi DockerfileDockerfileFROM ruby:2.6.3 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN mkdir /sample WORKDIR /sample ADD Gemfile /sample/Gemfile ADD Gemfile.lock /sample/Gemfile.lock RUN bundle install ADD . /sample【3】Gemfile、空のGemfile.lockを作成
$ vi GemfileGemfilesource 'https://rubygems.org' gem 'rails', '5.2.3'$ touch Gemfile.lock【4】docker-compose.ymlの作成
$ vi docker-compose.ymldocker-compose.ymlversion: '3' services: db: image: mysql command: mysqld --default-authentication-plugin=mysql_native_password environment: MYSQL_USER: root MYSQL_ROOT_PASSWORD: password ports: - '3316:3306' volumes: - ./db/mysql/volumes:/var/lib/mysql web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/sample ports: - "3000:3000" depends_on: - db【5】Rails newを実行する
$ docker-compose run web rails new . --force --database=mysql【6】イメージを再ビルドする
$ docker-compose build【7】config/database.ymlの設定
config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: host: localhostdatabase.ymlが上記のようになっているので
下記のように書き換える。config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password host: db【8】サービスを起動する
$ docker-compose up -d【9】DBを作成する
$ docker-compose run web rails db:create【10】アクセスしてサーバーの起動を確認する
http://localhost:3000
にアクセスしてRailsのトップページが表示されたら完成です。その後、サーバーを止める時は
docker-compose stop
とします。感じたこと
まだまだDockerに関する知識は浅いですがとにかく環境を構築する事ができました!
環境構築中MySQLの認証プラグインのエラーが発生してかなり苦戦したのですが、
どうやらMySQL8.0は
【4】docker-compose.ymlの作成のところで
command: mysqld --default-authentication-plugin=mysql_native_password
を書かないとエラーが出てしまうようです。
このエラーを解決する為に様々な方法を試していくうちに
MySQLの権限エラーが新たに出るなどして泥沼にハマっていき
10時間ぐらいかかって解決して何とか環境構築できました(汗)環境構築はとりあえずできましたが
何がどうなっているのかなどまだまだDockerを理解できていない部分が多いので
今後更に深く学習してみようと感じました。参考
[Rails] DockerでRails + MySQLの開発環境をつくる手順
丁寧すぎるDocker-composeによるrails + MySQL on Dockerの環境構築(Docker for Mac)
Docker Composeのインストール方法(CentOS7.3)
- 投稿日:2019-09-29T19:01:50+09:00
【初心者】Docker+Rails+MySQLでの環境構築の記録
はじめに
cloud9やvagrant以外にも
Dockerを使って環境構築してみたいと思い、勉強をしてみました!
Docker環境構築をする上で行った事を振り返ってまとめています。Dockerについて
- ホストOSのカーネルを使用している為、軽量で起動が高速
- 複数人で開発する時に開発環境を統一しやすい
- そのまま本番サーバーにデプロイする事ができる
などのメリットがあると知り、
様々の方のqiita記事など参考に環境構築に取り組んでみました。環境
- Mac OS
- Ruby 2.6.3
- Rails 5.2.3
- MySQL 8.0
【1】作業ディレクトリの作成、移動
$ mkdir sample $ cd sample【2】Dockerfileの作成
$ vi DockerfileDockerfileFROM ruby:2.6.3 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN mkdir /sample WORKDIR /sample ADD Gemfile /sample/Gemfile ADD Gemfile.lock /sample/Gemfile.lock RUN bundle install ADD . /sample【3】Gemfile、空のGemfile.lockを作成
$ vi GemfileGemfilesource 'https://rubygems.org' gem 'rails', '5.2.3'$ touch Gemfile.lock【4】docker-compose.ymlの作成
$ vi docker-compose.ymldocker-compose.ymlversion: '3' services: db: image: mysql command: mysqld --default-authentication-plugin=mysql_native_password environment: MYSQL_USER: root MYSQL_ROOT_PASSWORD: password ports: - '3316:3306' volumes: - ./db/mysql/volumes:/var/lib/mysql web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/sample ports: - "3000:3000" depends_on: - db【5】Rails newを実行する
$ docker-compose run web rails new . --force --database=mysql【6】イメージを再ビルドする
$ docker-compose build【7】config/database.ymlの設定
config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: host: localhostdatabase.ymlが上記のようになっているので
下記のように書き換える。config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password host: db【8】サービスを起動する
$ docker-compose up -d【9】DBを作成する
$ docker-compose run web rails db:create【10】アクセスしてサーバーの起動を確認する
http://localhost:3000
にアクセスしてRailsのトップページが表示されたら完成です。その後、サーバーを止める時は
docker-compose stop
とします。感じたこと
まだまだDockerに関する知識は浅いですがとにかく環境を構築する事ができました!
環境構築中MySQLの認証プラグインのエラーが発生してかなり苦戦したのですが、
どうやらMySQL8.0は
【4】docker-compose.ymlの作成のところで
command: mysqld --default-authentication-plugin=mysql_native_password
を書かないとエラーが出てしまうようです。
このエラーを解決する為に様々な方法を試していくうちに
MySQLの権限エラーが新たに出るなどして泥沼にハマっていき
10時間ぐらいかかって解決して何とか環境構築できました(汗)環境構築はとりあえずできましたが
何がどうなっているのかなどまだまだDockerを理解できていない部分が多いので
今後更に深く学習してみようと感じました。参考
[Rails] DockerでRails + MySQLの開発環境をつくる手順
丁寧すぎるDocker-composeによるrails + MySQL on Dockerの環境構築(Docker for Mac)
Docker Composeのインストール方法(CentOS7.3)
- 投稿日:2019-09-29T18:21:09+09:00
カレンダーの上で同一の時間帯のタスクの登録/更新をできないようにする
何がしたいか
環境
Rails v5.2.3
ruby v2.6.3p62gem
fullcalendar-rails v3.9テーブル構造概要
どうやって実現しよう...
modelにカスタムバリデーションを追加してエラーにしたい。
(controllerで行うと責務が増えすぎる気もした)
バリデーションでDBに保存済みの時間と画面から入力された時間を比較し、件数が0件か1件以上あるかで実装を頑張る。条件としては
DBに保存済みの開始時間<入力された開始時間or入力された終了時間<DBに保存されている終了時間
にエラーとするべきと考えた。
⇨SQLでの抽出条件:
"DBに保存済みの開始時間 < 画面から入力された開始時間
AND 画面から入力された開始時間 < DBに保存されている終了時間
OR DBに保存済みの開始時間 < 画面から入力された終了時間
AND 画面から入力された終了時間 < DBに保存されている終了時間"実装したソース
controller.rbdef create #新規登録 act_history = current_user.activity_historys.build( activity_name: @json_hash[:activity_name], from_time: @json_hash[:from_time], to_time: @json_hash[:to_time], remarks: @json_hash[:remarks], action: "create", #アクセサにセット current_user: current_user #アクセサにセット ) ...(略) end def update #更新 ...(略) respond_to do |format| if act_history.update( activity_name: @json_hash[:activity_name], from_time: @json_hash[:from_time], to_time: @json_hash[:to_time], remarks: @json_hash[:remarks], action: "update", #アクセサにセット current_user: current_user #アクセサにセット ) ...(略) endmodel.rb# アクセサ attr_accessor :action, :current_user # バリデーション validate :check_time ...(略) private def check_time ...(略) same_time_zone_exists end ...(略) # 画面から受け取った開始時間、終了時間をもとにすでにDBに存在しているか検証する def same_time_zone_exists exists_act_historys = current_user.activity_historys.where( #SQL抽出条件 "(from_time < ? AND ? < to_time OR from_time < ? AND ? < to_time)", from_time, from_time, to_time, to_time ) if action == "create" && exists_act_historys.count > 0 errors.add(:from_time, ": 同一時間帯の登録はできません") errors.add(:to_time, ": 同一時間帯の登録はできません") elsif action == "update" && exists_act_historys.count > 0 exists_act_historys.pluck(:id).each do |exists_ids| if exists_ids != id errors.add(:from_time, ": 同一時間帯の登録はできません") errors.add(:to_time, ": 同一時間帯の登録はできません") end end end end実装後
抽出条件が足りない模様。
入力された開始時間<DBに保存済みの開始時間〜終了時間<入力された終了時間
⇨SQLでの抽出条件:
"画面から入力された開始時間 < DBに保存済みの開始時間
AND 画面から入力された終了時間 < DBに保存されている終了時間"完成版ソース
model.rb# 画面から受け取った開始時間、終了時間をもとにすでにDBに存在しているか検証する def same_time_zone_exists exists_act_historys = current_user.activity_historys.where( #抽出条件追加 "(from_time < ? AND ? < to_time OR from_time < ? AND ? < to_time) OR (? < from_time AND to_time < ?)", from_time, from_time, to_time, to_time, from_time, to_time ) if action == "create" && exists_act_historys.count > 0 errors.add(:from_time, ": 同一時間帯の登録はできません") errors.add(:to_time, ": 同一時間帯の登録はできません") elsif action == "update" && exists_act_historys.count > 0 exists_act_historys.pluck(:id).each do |exists_ids| if exists_ids != id errors.add(:from_time, ": 同一時間帯の登録はできません") errors.add(:to_time, ": 同一時間帯の登録はできません") end end end end感想
とりあえず実装はできたので良いが、結構処理が複雑になっているように見えるので、もっとスリムにしたいです。他に方法があったらご指摘ください
- 投稿日:2019-09-29T17:52:59+09:00
Watirのgemを使ってSelenium::WebDriver::Error::WebDriverErrorのエラーが出た時の対処法
- 投稿日:2019-09-29T17:50:08+09:00
Angular8+Rails5でアプリを作ってみる
AngularとRailsを組み合わせてみる
前回の記事ではAngular+NestJSの組み合わせについてピックアップをしましたが、今回はRuby on Rails5とAngular8の組み合わせでwebアプリを作成している記事があり、読んでいてとても面白い内容だったので翻訳に挑戦しました!どちらも個人的に気に入っているフレームワークなので、世の中にこの組み合わせでの開発が増えてくると嬉しいです。
Angular8とRails5 (*以下記事の翻訳)
あたなはどうか知らないが、学びやすく、素早く書けて、オープンソースという理由で私はRuby on Railsが大好きだ。また、Ivyというクールな新しいレンダーエンジンを導入したAngularの新バージョン、Angular8のリリースにもワクワクしている。
もしあなたが私と同じでこれら2つのフレームワークに関する興味をわかってくれて、でも、その素晴らしい2つをどう始めたら良いのかさっぱりわからない場合は、ぜひこれを読んでほしい。
さっそく初めてみよう!
慣習的にJavascriptフレームワークと一緒にRailsを使う場合は、webpackが必要とされていた。しかし、Railsにはwebpackerと呼ばれるこれにうってつけのgemがあり、Angularプロジェクトを動かしてくれる。しかし、最新バージョン(7系以上)のAngularでは内部でwebpackシステムを持っており、そのようなプロジェクト構成が、素晴らしいAngularCLIを使う妨げとなってしまうのだ。
その代わりに私たちがしなければならないのは、Angularからの出力ファイルを受け取り、それらを提供するコントローラを備えたAPIとしてのRailsアプリを使うことだ。
始める前に下記をインストールしてほしい。
- node (8.9以上)
- NPM (5.5.1以上)
- Angular CLI
- Rails 5+ (インストールにRVMまたはRbenvを使うことをおすすめします)
- Postgresql
1. Angularプロジェクトを作成する
好きなディレクトリの場所でターミナルを開いて、Rails/Angularプロジェクトを作成する。
ng new rails-angular-project
Angular routingを使うか聞かれたら、迷わず「YES」を押しちゃってください!(かなり良いですよ!)
その次に、好きなスタイルシートの形式を選択して下さい。(個人的にはSCSSで進めてます)
そして、全て完了したら先ほど作ったプロジェクトにジャンプしましょう!
cd rails-angular-project
2. Railsプロジェクトを作成する
では、現在のディレクトリにRailsプロジェクトを作成しましょう!
rails new . -database=postgresql
これによって、Angularプロジェクトの内部にRailsプロジェクトを作成し始めるが、全てのマージを拒否した場合、コンフリクトが発生する。
Railsプロジェクトが作成されたなら、.gitignoreファイルにいくつかファイルを追加する必要があります。
.gitignoreファイルを開いて、下記をコピペして下さい。# Ignore bundler config. /.bundle # Ignore all logfiles and tempfiles. /log/* /tmp/* !/log/.keep !/tmp/.keep # Ignore uploaded files in development /storage/* !/storage/.keep .byebug_history # Ignore master key for decrypting credentials and more. /config/master.key # Ignore application configuration /config/application.yml3. 上記2つを合体する
今の段階で、Angularプロジェクトと一緒になったRailsアプリとなっており、これはAPIとしてのRailsを使用し提供している一方で、Angularフレームワークを用いたSPAの利益も享受していることと言える。しかしその前にRailsにAngularが提供するファイルにアクセスできるようにしなければならない。
通常はAngularアプリでは、
ng serve
を使用して開発を進めるのだが、今回はng build
を使用してRailsのパブリックディレクトリに出力するように設定する。ルートディレクトリにある
Angular.json
を開いて、出力先ディレクトリをdist/rails-angular-app
からpublic
に変更する。angular.json... "options": { "outputPath": "public", ...次に、app/controllersに
static_controller.rb
という名前のファイルを作成する。app/controllers/static_controller.rbclass StaticController < Rails::ApplicationController def index render file: Rails.root.join('public', 'index.html') end endstatic_controllerはAngularによって提供される
index.html
をレンダリングする役目を持ちます。そして、下記のコードを
routes.rb
に追加して下さい。routes.rbRails.application.routes.draw do get '*other', to: 'static#index' endこれで、Railsにルートをキャッチさせて、static_controllerにリダイレクトすることができます。また、上記のコードによってapiに必要となるどのルートも定義することができるのです。でもそれは次の機会に回しましょう!
4. Foremanを使って便利にする
Foremanを導入することによって、Webアプリを提供しやすくしましょう。
gemに
foreman
を追加することによってインストールして下さい。Gemfile... # Use foreman gem 'foreman' ...
bundle install
で新しいgemをインストールします。
bundle install
Procfileと呼ばれるファイルを新しくルートディレクトリに追加します。これは組み合わせされる全てのコマンドをforemanが読み込めるようにするためのファイルです。
Procfileweb: rails s -p 3000 client: ng build --watch=trueこれでforemanを起動できます。
foreman start
RailsアプリとAngularアプリが同時に起動されるはずです。そして、
localhost:3000
にアクセスすると以下のような画面が表示されるでしょう。完了です!これで自由にWebアプリを開発することができますし、apiにシンプルかつパワフルなフレームワークを与えるのと同時に、Angular CLIの力を用いることができます。
5.終わりに
今回、Rails5のバックエンドとAngular8のフロントエンドで、同じディレクトリに存在し、それぞれの力を最大限に活用するアプリケーションを作りました。このチュートリアルがあなたにとって役に立てれば幸いです。
参考記事
- 投稿日:2019-09-29T17:27:16+09:00
Rails 6 に Bootstrap3 導入
1. yarn で Bootstrap と jquery を導入する
$ yarn add bootstrap-sass jquery
2. app/config/webpack/environment.js に jquery が使えるように設定追加
app/config/webpack/environment.jsconst { environment } = require('@rails/webpacker') const webpack = require('webpack') environment.plugins.append('Provide', new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }) ) module.exports = environment3. app/javascript/packs/application.js に 以下追記
app/javascript/packs/application.jsimport '../javascripts/application.js' import '../stylesheets/application.scss'4. app/javascript/packs/stylesheets/application.scss に以下追記
app/javascript/packs/stylesheets/application.scss// bootstrap-sass 内部で 相対パスを解決できないため、パス変数を差し替え $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/"; @import "~bootstrap-sass/assets/stylesheets/_bootstrap.scss"5. app/javascript/javascripts/application.js に以下追記
app/javascript/javascripts/application.jsimport 'bootstrap-sass';
- 投稿日:2019-09-29T16:37:40+09:00
洋書多読コミュニティサイト「多読マラソン」をリリースしました!
はじめに
以前、こういうWebサイトをリリースしました。(リンクは外しています)
多読マラソンサイト名は、多読マラソンです。ネーミングセンスとかは突っ込まないでいただけると嬉しいですね!
制作経緯
僕は大学受験時代に洋書を読むことにハマり、そこから受験勉強そっちのけで英語の本を読みまくってきました。いわゆる多読というやつですね。洋書をとにかくたくさん読むと。
一時期、TOEICの勉強をやっていたこともあるんですが、あまりにつまらなすぎて1ヶ月立たずに挫折してしまいました。そんな僕ですが、この洋書多読にはハマりました。これがとにかく楽しい。
ちなみに、洋書多読にハマったおかげで、ぶっつけ本番で受けたTOEICでも900点突破できたので、本当、多読のおかげで人生変わったと言っても過言ではないです。僕の場合は。
で、この多読界隈では、読んだ洋書の量を「語数」で表すんですね。100万語突破とか、500万語突破とか。200〜300ページの洋書がだいたい50000語なので、100万語突破だと、普通の洋書を20冊読んだ計算になります。
多読実践者の間では、100万語読むことがベンチマークとされていまして、皆この数字を目標にがんばります。そして、この語数を記録するために、いろいろなサービスがあるわけなのですが、僕も例に漏れず、あるwebサイトを使用していました。
ところがこのサイト、ところどころ使いづらいんですね。あまりメンテナンスもされてなさそうで、正直なんだかなーという思いを感じていました。これが受験生の頃ですね。
それから数年が経ち、プログラミングを初めて1年半にもなりまして、あるときこう思ったわけなんですね。「ぶっちゃけ今の俺なら、もっと良いサイト作れるんじゃねーの?」と。
制作風景
制作期間は約3週間。2019年9月10日から制作を開始して、この記事を書いているのが9月29日。サイトの規模的に、10日ぐらいで完成させたかったんですが、インターンをしていたので思うように時間が取れなかったことと、サービスが完成に近づくにつれて、ここ直したい!、という箇所が増えて、3歩進んで3歩下がる的な状況に陥ってしまい、なかなか遠かったです。
ですが!
ひとまずMVP的なものにはなったので、一旦リリースしてみようか、って感じです。仕様
Amazon Product Advertising API
を使ったサービスとなっています。これがなきゃ成立しません。使用技術
- Amazon Product Advertising API
- Ruby on Rails
- HTML(Slim), CSS(Sass)
- DB
- Postgresql
- CircleCI
- Docker
- Heroku(独自ドメイン)
感想
ちょっとRailsに飽きてきた感がありましたw
中小規模のサービスをちゃちゃっと開発するのにはRails最強だと思うんですが、あまりに便利すぎて、なんか堕落してるような気分になるのが玉に瑕ですかねw
参考文献・サイト・ライブラリ
- 参考文献・サイト
- 投稿日:2019-09-29T11:15:00+09:00
[Rails]stringからenumに変更する
stringで保存されたデータをenumに変更します。
enumの設定
ゴールとなるenumの設定。
Reservation.rbclass Reservation < ApplicationRecord enum status: { applied: 0, accepted: 1, cancelled: 2, completed: 3 } endmigrationの設定
元データは、
Reservation.status
にstringで'applied','accepted','cancelled','completed'
が入っています。以下のようなマイグレーションで、カラム型をStringからIntegerに変更し、データも移行するところまで一括で行います。
class ReservationStatusConvertToEnum < ActiveRecord::Migration[6.0] # データベース操作の際にコールバックを呼ばないよう、テーブルを使うだけのTmpモデルを作る class TmpReservation < ApplicationRecord self.table_name = :reservations end def change # 元データのカラム名を変更 change_table :reservations do |t| t.rename :status, :old_status end reversible do |dir| dir.up do # 新しくenum用のintegerカラムを追加 add_column :reservations, :status, :integer, null: false, default: 0 # テーブル情報をリセットする TmpReservation.reset_column_information # stringで持っていた名前をenumと同じ順でarrayにし、旧データをインデックス番号で取る TmpReservation.find_each do |tr| tr.status = %w(applied accepted cancelled completed).index(tr.old_status) tr.save! end # 旧データを捨てる remove_column :reservations, :old_status end # upの逆 dir.down do add_column :reservations, :old_status, :string TmpReservation.reset_column_information TmpReservation.find_each do |tr| r = Reservation.find(tr.id) tr.old_status = r.status tr.save! end remove_column :reservations, :status end end end end参考: Rails Migration, converting from string to enum | stack overflow
Scopeの修正
条件をSQL形式で書いてしまっているところは、enumに変換後に期待するデータが取ってこれなくなりますので、修正します。
scope :applied_reservations, -> { where("status = 'applied'") } # NG scope :applied_reservations, -> { where(status: 'applied') } # OK
- 投稿日:2019-09-29T09:33:50+09:00
rails 条件付きでバリデーションを効かせる
こういうときはバリデーション効かせないで欲しい
ってときの対応。
通常のバリデーションは以下の感じ
class User < ApplicationRecord validates :name, presence: true validates :email, presence: true, length: {maximum: 255}, uniqueness: { case_sensitive: false} validates :password, presence: true, confirmation: true has_many :questionsどういう状況で使う?
例えばSNSログインの場合、twitterでやろうとするとemailの値が取得できない(2019/08/10現在)のでバリデーションが効くとエラーになる。。。
⇨「SNSログインの際に取得するuidカラムに値が入っていないときは通常のログインとみなしてバリデーションを効かせたい。」時
if
やunless
で条件を指定して、目的の項目へ追加する。class User < ApplicationRecord validates :name, presence: true validates :email, presence: true, length: {maximum: 255}, uniqueness: { case_sensitive: false}, unless: :uid? #email取れないからこんな感じ validates :password, length: {minimum: 6}, presence: true, confirmation: true, unless: :uid? #パスワードもSNSログインと別管理ならこんな感じ has_many :questionscallbackに対しても指定可能
以下はバリデーション用のヘルパーを定義してそのヘルパーの中で
unless
を使い処理をするかしないかを判定している。class User < ApplicationRecord validates :name, presence: true, length: {maximum: 50} #以下でヘルパーを呼び出す before_save :email_downcase VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: {maximum: 255}, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false}, unless: :uid? #こんな感じ validates :password, length: {minimum: 6}, presence: true, confirmation: true, unless: :uid? has_many :questions #email用にヘルパーを定義 def email_downcase unless uid? self.email.downcase! end endとりあえず、こんなけ。
- 投稿日:2019-09-29T06:14:55+09:00
【Rails】 controller内で共通の処理をメソッド化してbefore_actionで呼び出す
環境
rails 5.2.3
ruby 2.5.1概要
コントローラー内で共通の処理を書いていたら、メンターにメソッド化しましょうと言われたので、脱・初心者を目指し、備忘録。
メンターレビュー前
hogehoge_controller.rbclass hogehogeController < ApplicationController def new @user = User.find(params[:user_id]) @evaluation_all = Evaluation.group(:seller_id).size[@user.id] @evaluation = Evaluation.new end def create @user = User.find(params[:user_id]) @evaluation = Evaluation.new(evaluation_params) if @evaluation.save redirect_to introduction_user_path(@user.id), notice: "評価が送信されました" else redirect_to introduction_user_path, notice: "評価が正常に送信できませんでした。もう一度行ってください。" end end private def user_params params.require(:user) end def evaluation_params params.require(:evaluation).permit(:rating, :comment).merge(buyer_id: current_user.id, seller_id: @user.id) end end指摘されたのはこの部分。
@user = User.find(params[:user_id])同じ記述が2回出てきていますね。
この@userの処理は共通のものなので、分けましょう、と言われたわけです。またこの処理は外部から呼び込まれる必要はないので、private以下に追記します。
private def set_user @user = User.find(params[:user_id]) endこうすることで、呼び出しがクラス内部に限定されるんですね。
privateについてはこちらの記事が勉強になりました。ありがとうございます。
Ruby の private と protected 。歴史と使い分けさて、あとは各メソッドが実行される時に呼び出されると善きですね。
そんな時は before_actionを使います。
class EvaluationsController < ApplicationController before_action :set_user, only: [:new, :create] def new hogehoge...before_action [メソッド名],[おぷしょん]
onlyは、どのメソッドが呼ばれた時にbefore_actionを実行するのかを示し、
expect は、指定したメソッドが呼ばれた時は実行しません。今回は、newとcreateの実行前にやってほしい処理ですからこうします。
before_action :set_user, only: [:new, :create]メンターレビュー後
...というわけで直したのがこちら
hogehoge_controller.rbclass EvaluationsController < ApplicationController before_action :set_user, only: [:new, :create] def new @evaluation_all = Evaluation.group(:seller_id).size[@user.id] @evaluation = Evaluation.new end def create @evaluation = Evaluation.new(evaluation_params) if @evaluation.save redirect_to introduction_user_path(@user.id), notice: "評価が送信されました" else redirect_to introduction_user_path, notice: "評価が正常に送信できませんでした。もう一度行ってください。" end end private def set_user @user = User.find(params[:user_id]) end def user_params params.require(:user) end def evaluation_params params.require(:evaluation).permit(:rating, :comment).merge(buyer_id: current_user.id, seller_id: @user.id) end end