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

起動中のrailsコンテナに入るシェルスクリプトのメソッド

起動中のrailsコンテナに入るシェルスクリプトのメソッド

zshrc.
function dbash() {
    id=$(docker ps -q --filter "name=web")
    command docker exec -it $id bash
}

参考

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

Amazon LinuxでRMagick 3.0.0 がインストールできない

bundle updateの際にrmagickのインストールで詰まってしまったため、対処法のメモ。

発生した問題

Capistranoでrails製アプリケーションのデプロイ中、rmagickのインストールでエラーが発生した。

00:14 bundler:install
      01 $HOME/.rbenv/bin/rbenv exec bundle install --path /var/www/taimee-rails-api/shared/bundle --jobs 4 --without development test --deployment --quiet
      01 Gem::Ext::BuildError: ERROR: Failed to build gem native extension.
      01
      01 current directory:
      01 /var/www/app/shared/bundle/ruby/2.6.0/gems/rmagick-3.0.0/ext/RMagick
      01 /home/client/.rbenv/versions/2.6.1/bin/ruby -I
      01 /home/client/.rbenv/versions/2.6.1/lib/ruby/2.6.0 -r
      01 ./siteconf20190326-11939-1i8on2k.rb extconf.rb
      01 checking for gcc... yes
      01 checking for Magick-config... yes
      01 checking for outdated ImageMagick version (<= 6.8.9)... *** extconf.rb failed
      01 ***
      01 Could not create Makefile due to some reason, probably lack of necessary
      01 libraries and/or headers.  Check the mkmf.log file for more details.  You may
      01 need configuration options.
      01
      01 Provided configuration options:
      01    --with-opt-dir
      01    --without-opt-dir
      01    --with-opt-include
      01    --without-opt-include=${opt-dir}/include
      01    --with-opt-lib
      01    --without-opt-lib=${opt-dir}/lib
      01    --with-make-prog
      01    --without-make-prog
      01    --srcdir=.
      01    --curdir
      01    --ruby=/home/client/.rbenv/versions/2.6.1/bin/$(RUBY_BASE_NAME)
      01
      01 To see why this extension failed to compile, please check the mkmf.log which can
      01 be found here:
      01
      01 /var/www/app/shared/bundle/ruby/2.6.0/extensions/x86_64-linux/2.6.0-static/rmagick-3.0.0/mkmf.log
      01
      01 extconf failed, exit code 1
      01
      01 Gem files will remain installed in
      01 /var/www/app/shared/bundle/ruby/2.6.0/gems/rmagick-3.0.0 for
      01 inspection.
      01 Results logged to
      01 /var/www/app/shared/bundle/ruby/2.6.0/extensions/x86_64-linux/2.6.0-static/rmagick-3.0.0/gem_make.out
      01
      01 An error occurred while installing rmagick (3.0.0), and Bundler cannot continue.
      01 Make sure that `gem install rmagick -v '3.0.0' --source 'https://rubygems.org/'`
      01 succeeds before bundling.
      01
      01 In Gemfile:
      01   rmagick

エラーログの指示通り、 /var/www/app/shared/bundle/ruby/2.6.0/extensions/x86_64-linux/2.6.0-static/rmagick-3.0.0/mkmf.log を確認

$ cat /var/www/app/shared/bundle/ruby/2.6.0/extensions/x86_64-linux/2.6.0-static/rmagick-3.0.0/mkmf.log
find_executable: checking for gcc... -------------------- yes

--------------------

find_executable: checking for Magick-config... -------------------- yes

--------------------

Detected ImageMagick version: 6.7.8
Can't install RMagick 3.0.0. You must have ImageMagick 6.8.9 or later.
Can't install RMagick 3.0.0. You must have ImageMagick 6.8.9 or later.

ImageMagickのバージョンが古いのが原因のようですね。バージョンを上げましょう

ImageMagickのバージョンを上げる

RMagickはImageMagick7系をサポートしていないので、6.9系をインストールします。

Remiのリポジトリをインストール

ImageMagickの6.9系のバージョンが配布されているRemiリポジトリをインストールします。

//yumリポジトリにEPELを追加
yum install epel-release

//Remiのリポジトリ設定パッケージをダウンロード
wget http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

//Remiのリポジトリのインストール
rpm -Uvh remi-release-7.rpm

古いパッケージを削除

$ yum remove -y libtiff
$ yum remove -y graphviz
$ yum remove -y libwebp libwebp-devel
$ yum remove -y ImageMagick ImageMagick-devel ImageMagick-libs

新しいパッケージをインストール

$ yum install -y libtiff --enablerepo=remi,epel,base
$ yum install -y graphviz --enablerepo=remi,epel,base
$ yum install -y ImageMagick6 ImageMagick6-devel ImageMagick6-libs --enablerepo=remi,epel,base

バージョンの確認

$ convert --version
Version: ImageMagick 6.9.10-34 Q16 x86_64 2019-03-18 https://imagemagick.org
Copyright: © 1999-2019 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC Modules OpenMP
Delegates (built-in): bzlib cairo djvu fftw fontconfig freetype gslib gvc jbig jng jp2 jpeg lcms ltdl lzma openexr pangocairo png ps raw rsvg tiff webp wmf x xml zlib

6.9.10をインストールできました。この後 bundle install を実行すると無事RMagickのインストールができました。
以上です。

参考

http://webdew.hateblo.jp/entry/2016/05/19/044105
https://qiita.com/iharakenji/items/dc9a2ce254dcdd2e97a0

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

OSS初心者がRailsにプルリクエストを送ってマージされるまでの一部始終

はじめに

本稿はOSSにプルリクエストを送ったことがない方に対する「自分はこういう方法でプルリクエストを送ったぞ!」という体験記として投稿させて頂きます。初心者であってもOSSに興味がある方にはご一読頂ければ嬉しいです。

前提

Githubを使って開発をしたことがある前提の内容が一部含まれます :pray:
(これからの方にもなるべく分かるように書きますが念のため)

今回出来たこと

Rails本体に送ったプルリクエストがマージされました:tada:
image.png

Rails Contributors掲載サイトに載った
image.png

オレンジの部分が自分の名前 :see_no_evil: :sweat_drops: (畏れ多い)
image.png

OSSにプルリクエストを送る手順

OSSごとにコントリビューション(貢献)におけるガイドラインがあるので、まずそちらに目を通しましょう :eyes:
※ Railsの場合は以下を参照!
https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html

目次

  1. プルリクエストを送りたいリポジトリをForkする
  2. 自分の手元のリポジトリを確認
  3. 自分の手元にForkしたリポジトリをローカルにcloneする
  4. ローカルでブランチを切る
  5. 修正をコミット
  6. テストが通ることを確認
  7. git remote -vでリモートとして登録されているリポジトリを確認する
  8. 本体をupstream,自分のリポジトリをoriginとして登録する
  9. もう一度remoteとして登録されているリポジトリを確認
  10. origin(自分の作業リポジトリ)へプッシュする
  11. 本体のリポジトリを開いてプルリクを出す
  12. マージを待つ

1. プルリクエストを送りたいリポジトリをForkする

※ Forkが分からない方はこちらを参照 :リポジトリのcloneとforkの違い

image.png

2. 自分の手元のリポジトリを確認

image.png

3. 自分の手元にForkしたリポジトリをローカルにcloneする

$ git clone git@github.com:Shigeyuki-fukuda/rails.git

4. ローカルでブランチを切る

$ git checkout -b ブランチ名

5. 修正をコミット

$ git commit -m "英語のコミットメッセージ"

6. テストが通ることを確認

テストを実行したところテストが落ちてたのですが...
修正と関係ないところだったので、今回は一旦、無視しました :bow: :sweat_drops:
(この修正の時master自体のCI落ちてた?のもあり一旦スルー)
通常は注視した方がいいかもです:pray:

$ rake test

7. git remote -vでリモートとして登録されているリポジトリを確認する

Shigeyuki-fukuda/rails がremoteのoriginとして登録されていることを確認。

※ Git初心者の方へ

remoteというのはコミットして修正をプッシュする目的地というイメージでOKです :ok_hand:
例えば、 git push origin masterorigin 部分のこと

$ git remote -v
origin  git@github.com:Shigeyuki-fukuda/rails.git (fetch)
origin  git@github.com:Shigeyuki-fukuda/rails.git (push)

8. 本体をupstream,自分のリポジトリをoriginとして登録する

OSSの慣習で、元のリポジトリをupstreamとし、自分の手元をoriginとして登録するみたいです。

$ git remote add upstream https://github.com/rails/rails.git
$ git remote add origin git@github.com:Shigeyuki-fukuda/rails.git

9. もう一度remoteとして登録されているリポジトリを確認

これでFork元の rails/rails の方がupstreamならOKです。

$ git remote -v
origin  git@github.com:Shigeyuki-fukuda/rails.git (fetch)
origin  git@github.com:Shigeyuki-fukuda/rails.git (push)
upstream  https://github.com/rails/rails.git (fetch)
upstream  https://github.com/rails/rails.git (push)

10. origin(自分の手元の作業リポジトリ)へプッシュする

origin へプッシュする

$ git push origin 修正ブランチ名

11. 本体のリポジトリを開いてプルリクを出す

キャプチャ忘れてしまったのですが、本体のリポジトリを開くとプルリクを出せるぞ!という表示が通常通り出ます。
Fork先にプッシュしても本体にプルリク出せるよ!の表示が出るのでそこからプルリクを作ります。

こんな感じのやつ↓↓↓

image.png

当たり前ですが説明文は英語なので頑張りましょう:muscle:
※ ブラウザで出来る英文チェッカー
http://lifeiscolourful.hatenablog.com/entry/grammarly-n-writing

image.png

12. マージを待つ

無事にプルリクエストがマージされたことを確認しました :sob:

image.png

補足:remoteの最新をoriginに取り込みたいとき

git fetchでupstream(元のリポジトリ)の最新を取ってくる

$ git fetch upstream

upstream(元のリポジトリ)の最新をorigin(自分の手元)にマージする

$ git merge upstream/master

remoteの自分管理のリポジトリを最新にしたい場合はそのままプッシュする
※ 絶対に元のリポジトリ(upstream)のmasterにプッシュしてはいけません :bow: (ダメゼッタイ)

$ git push origin master

最新の状況になったことを確認します :eyes:

image.png

最後に

はじめる前は、すごい複雑で一部の「選ばれし人々」によって行われる活動がOSSだと思ったのですが、やること自体はシンプルでした。「ここ直した方がいいかも?」と思ったところを修正して、それがマージされるのは皆んなの公園を綺麗にしたみたいで気分がいいですね!!
皆さんもぜひOSSに一歩踏み出してみてはいかがでしょうか?
最後に宣伝になりますが、私が運営メンバーの一人をしているTama.rbという地域RubyコミュニティでもOSSのコードを読んで皆んなでワイワイする活動をはじめました。
OSS活動はじめたいけど一人だけだと、どうして良いのか自信ない...って方はTama.rbのslackだけでも参加頂けると嬉しいです!皆んなでワイワイOSS活動を楽しみましょう:relaxed:

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

プログラミング学習記録39〜Herokuにデプロイ〜

今日やったこと

- Progate Railsのレッスンで作ったアプリをHerokuにデプロイする

以前、Udemyの講座で作ったToDoアプリをHerokuにデプロイしたことがあったので、同じ手順でやればできるはずだと思ってやったらできました。

1回目はデータベース関連の操作で何かでミスって動かなくなってしまったのですが、2回目でコマンドを注意深く確認しながらやったらうまくいきました。

これでCloud9で環境構築してコードを書いてからHerokuにデプロイするまでの流れはだいぶ掴めました。

やっぱり実際に手を動かしてやっていくのがいいですね。

簡単なアプリとはいえ、デプロイまでできたらだいぶ達成感があるのでオススメです。

明日からは今までやってきた基礎を復習しつつ、オリジナルアプリ制作に入っていきたいと思います。

SQLとGitもなんとなく触っているので、もう少しじっくりやってみる必要がありそうです。

ということで、明日からも引き続きプログラミング学習頑張ります。

おわり

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

railsチュートリアル 第十四章 

はじめに

この記事では、railsチュートリアル第14章の復習として行程をようやくしたものとなっています。

ユーザーをフォローする

第14章では他のユーザーをフォロー (およびフォロー解除) できるソーシャルな仕組みの追加と、フォローしているユーザーの投稿をステータスフィードに表示する機能を追加していく。

Relationshipモデル

ユーザーをフォローする機能を実装する第一歩は、データモデルを構成することとなる。

「1人のユーザーが複数のユーザーをhas_manyとしてフォローし、1人のユーザーに複数のフォロワーがいることをhas_manyで表す」

といった方法でも実装できそうだが、これでは問題が出てきてしまう。

上記のようにテーブル構造すると、非常に無駄が多く更新も大きな手間となってしまう事になる。

この問題の根本は、必要な抽象化を行なっていないことであり、正しいモデルを見つけ出す方法の1つは、Webアプリケーションにおける following の動作をどのように実装するかを観察することである。

ここを観察すると、フォローまたはアンフォローで作成または削除されるのは、つまるところ2人のユーザーの「関係 (リレーションシップ)」であることがわかる。

リレーションシップを経由することによって1人のユーザーは1対多の関係を持つことができ、さらにユーザーは多くのfollowing (またはfollowers) と関係を持つことができるということになる。

また、Facebookのような友好関係 (Friendships) では本質的に左右対称のデータモデルが成り立つが、Twitterのようなフォロー関係では左右非対称の性質がある。

このような左右非対称な関係性を見分けるために、それぞれを能動的関係 (Active Relationship)受動的関係 (Passive Relationship)と呼ぶことにする。

まずは、フォローしているユーザーを生成するために、能動的関係に焦点を当てていく。

フォローしているユーザーはfollowed_idがあれば識別することができるので、active_relationshipsテーブルを作り、そこを経由することによって効率的なモデル構造を作る。

このデータモデルを実装するために、まずは次のようなマイグレーションを生成する。

$ rails generate model Relationship follower_id:integer followed_id:integer

このリレーションシップは今後 follower_idfollowed_id で頻繁に検索することになるから、それぞれのカラムにインデックスを追加しておく。

db/migrate/[timestamp]_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.0]
  def change
    create_table :relationships do |t|
      t.integer :follower_id
      t.integer :followed_id

      t.timestamps
    end
    add_index :relationships, :follower_id
    add_index :relationships, :followed_id
    add_index :relationships, [:follower_id, :followed_id], unique: true
  end
end

↑のマイグレーションファイルの、

add_index :relationships, [:follower_id, :followed_id], unique: true

という複合キーインデックスは、follower_idとfollowed_idの組み合わせが必ずユニークであることを保証する仕組みであり、これにより、あるユーザーが同じユーザーを2回以上フォローすることを防ぐ。

relationshipsテーブル を作成するために、いつものようにデータベースのマイグレーションを行う。

$ rails db:migrate

User/Relationshipの関連付け

フォローしているユーザーとフォロワーを実装する前に、UserとRelationshipの関連付けを行う。

1人のユーザーにはhas_many (1対多) のリレーションシップがあり、このリレーションシップは2人のユーザーの間の関係となるから、フォローしているユーザーとフォロワーの両方に属す。 (belongs_to)

今回のケースではフォローしているユーザーを follower_id という外部キーを使って特定しなくてはならない。

また、followerというクラス名は存在しないので、ここでもRailsに正しいクラス名を伝える必要がある。

能動的関係に対して1対多 (has_many) の関連付けを実装する

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
  .
  .
  .
end

上記では、ユーザーを削除したら、ユーザーのリレーションシップも同時に削除される必要あるため、関連付けにdependent: :destroyも追加している。

followerの関連付けについては現段階では使わないが、followerfollowed を対称的に実装しておくことで、構造に対する理解は容易になる。

app/models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
end

上記で定義した関連付けにより下のようなメソッドが使用できるようになった。

メソッド 用途
active_relationship.follower フォロワーを返す
active_relationship.followed フォローしているユーザーを返す
user.active_relationships.create(followed_id: other_user.id) userと紐付けて能動的関係を作成/登録する
user.active_relationships.create!(followed_id: other_user.id) userを紐付けて能動的関係を作成/登録する (失敗時にエラーを出力)
user.active_relationships.build(followed_id: other_user.id) userと紐付けた新しいRelationshipオブジェクトを返す

Relationshipのバリデーション

ここで存在性のバリデーションを与えておく。

app/models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
  validates :follower_id, presence: true
  validates :followed_id, presence: true
end

フォローしているユーザー

followingとfollowersに関して今回はhas_many throughを使う。

デフォルトのhas_many throughという関連付けでは、Railsはモデル名 (単数形) に対応する外部キーを探す。

has_many :followeds, through: :active_relationships

上のコードの場合、Railsは「followeds」というシンボル名を見て、これを「followed」という単数形に変え、 relationshipsテーブルのfollowed_idを使って対象のユーザーを取得してくる。

ただ、user.followedsという名前は英語として不適切となる。

代わりに、user.followingという名前を使うことにする。
そのためには、Railsのデフォルトを上書きする必要があり、ここでは:sourceパラメーターを使って「following配列の元はfollowed idの集合である」ということを明示的にRailsに伝える必要がある。
Userモデルにfollowingの関連付けを追加する↓

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
  #追加
  has_many :following, through: :active_relationships, source: :followed
  .
  .
  .
end

次に、followingで取得した集合をより簡単に取り扱うために、followunfollow といった便利メソッドを追加する。
"following" 関連のメソッド ↓

app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  def feed
    .
    .
    .
  end

  # ユーザーをフォローする
  def follow(other_user)
    following << other_user
  end

  # ユーザーをフォロー解除する
  def unfollow(other_user)
    active_relationships.find_by(followed_id: other_user.id).destroy
  end

  # 現在のユーザーがフォローしてたらtrueを返す
  def following?(other_user)
    following.include?(other_user)
  end

  private
  .
  .
  .
end

フォロワー

ここでuser.followersメソッドを追加していく。

これは上のuser.followingメソッドと対になり、フォロワーの配列を展開するために必要な情報は、relationshipsテーブルに既にある。

よって、follower_idとfollowed_idを入れ替えるだけで、フォロワーについてもフォローする場合と全く同じ方法が活用できる。
受動的関係を使ってuser.followersを実装する↓

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships,  class_name:  "Relationship",
                                   foreign_key: "follower_id",
                                   dependent:   :destroy
  #追加
  has_many :passive_relationships, class_name:  "Relationship",
                                   foreign_key: "followed_id",
                                   dependent:   :destroy
  has_many :following, through: :active_relationships,  source: :followed
  #追加
  has_many :followers, through: :passive_relationships, source: :follower
  .
  .
  .
end

:followers属性 の場合、Railsが「followers」を単数形にして自動的に外部キーfollower_idを探してくれるから :sourceキー を省略してもイイが has_many :following との類似性を強調させるため残しておく。

[Follow] のWebインターフェイス

この節では、フォロー/フォロー解除の基本的なインターフェイスを実装、また、フォローしているユーザーと、フォロワーにそれぞれ表示用のページを作成する。

フォローのサンプルデータ

前の章のときと同じように、サンプルデータを自動作成する rails db:seed を使って、データベースにサンプルデータを登録しておく。

先にサンプルデータを自動作成できるようにしておけば、Webページの見た目のデザインから先にとりかかることができ、バックエンド機能の実装を後に回すことができるというメリットがある。

サンプルデータにfollowing/followerの関係性を追加する↓

db/seeds.rb
#省略

# リレーションシップ
users = User.all
user  = users.first
following = users[2..50]
followers = users[3..40]
following.each { |followed| user.follow(followed) }
followers.each { |follower| follower.follow(user) }

ここでは、最初のユーザーにユーザー3からユーザー51までをフォローさせ、それから逆にユーザー4からユーザー41に最初のユーザーをフォローさせる。

データベース上のサンプルデータを作り直すために、いつものコマンドを実行する。

$ rails db:migrate:reset
$ rails db:seed

統計と [Follow] フォーム

これでサンプルユーザーに、フォローしているユーザーとフォロワーができた。

プロフィールページHomeページを更新して、これを反映する。

次にUsersコントローラにfollowingアクションとfollowersアクションを追加する↓

config/routes.rb
Rails.application.routes.draw do
  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  #追加
  resources :users do
    member do
      get :following, :followers
    end
  end

  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  resources :microposts,          only: [:create, :destroy]
end

↑によって生成されるルーティングテーブルを下に示す

HTTPリクエスト URL アクション 名前付きルート
GET /users/1/following following following_user_path(1)
GET /users/1/followers followers followers_user_path(1)

ルーティングを定義したので、統計情報のパーシャルを実装する。

app/views/shared/_stats.html.erb
<% @user ||= current_user %>
<div class="stats">
  <a href="<%= following_user_path(@user) %>">
    <strong id="following" class="stat">
      <%= @user.following.count %>
    </strong>
    following
  </a>
  <a href="<%= followers_user_path(@user) %>">
    <strong id="followers" class="stat">
      <%= @user.followers.count %>
    </strong>
    followers
  </a>
</div>

<% @user ||= current_user %>
このコードは、@userがnilでない場合 (つまりプロフィールページの場合) は何もせず、nilの場合 (つまりHomeページの場合) には@userにcurrent_userを代入するコードとなる。

また、
<strong id="following" class="stat">
...
</strong>

こうしておくと、Ajaxを実装するときに便利となる。

これをHomeページに表示する。

app/views/static_pages/home.html.erb
<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      #追加
      <section class="stats">
        <%= render 'shared/stats' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3>Micropost Feed</h3>
      <%= render 'shared/feed' %>
    </div>
  </div>
<% else %>
  .
  .
  .
<% end %>

また必要に応じてSCSSにスタイルを加える。

次の行程として[Follow] / [Unfollow] ボタン用のパーシャルも作成しておく。

app/views/users/_follow_form.html.erb
<% unless current_user?(@user) %>
  <div id="follow_form">
  <% if current_user.following?(@user) %>
    <%= render 'unfollow' %>
  <% else %>
    <%= render 'follow' %>
  <% end %>
  </div>
<% end %>

このコードは、followunfollowのパーシャルに作業を振っているだけになるからRelationshipsリソース用の新しいルーティングを追加し、フォロー/フォロー解除用のパーシャルを個別に用意する必要がある。

Relationshipリソース用のルーティングを追加する↓

config/routes.rb
#省略

resources :relationships,       only: [:create, :destroy]
end

ユーザーをフォローするフォーム↓

app/views/users/_follow.html.erb
<%= form_for(current_user.active_relationships.build) do |f| %>
  <div><%= hidden_field_tag :followed_id, @user.id %></div>
  <%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>

ユーザーをフォロー解除するフォーム↓

app/views/users/_unfollow.html.erb
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
             html: { method: :delete }) do |f| %>
  <%= f.submit "Unfollow", class: "btn" %>
<% end %>

これでパーシャルとしてフォロー用フォームをプロフィールページに表示できるようになった。

プロフィールページにフォロー用フォームとフォロワーの統計情報を追加する↓

app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
    </section>
    #追加
    <section class="stats">
      <%= render 'shared/stats' %>
    </section>

  </aside>
  <div class="col-md-8">
    #追加
    <%= render 'follow_form' if logged_in? %>
    <% if @user.microposts.any? %>
      <h3>Microposts (<%= @user.microposts.count %>)</h3>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>

この[Follow] / [Unfollow] ボタンの実装には標準的な方法とAjaxを使う方法の2つがある。

その前に、フォローしているユーザーとフォロワーを表示するページをそれぞれ作成してHTMLインターフェイスを完成させる。

[Following] と [Followers] ページ

フォローしているユーザーを表示するページと、フォロワーを表示するページは、いずれもプロフィールページとユーザー一覧ページを合わせたもののようになる。

ここでの最初の作業は、フォローしているユーザーのリンクとフォロワーのリンクを動くようにすることとなる。

Twitterに倣って、どちらのページでもユーザーのログインを要求するようにする。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
                                        :following, :followers]
  .
  .
  .
  def following
    @title = "Following"
    @user  = User.find(params[:id])
    @users = @user.following.paginate(page: params[:page])
    render 'show_follow'
  end

  def followers
    @title = "Followers"
    @user  = User.find(params[:id])
    @users = @user.followers.paginate(page: params[:page])
    render 'show_follow'
  end

  private
  .
  .
  .
end

Railsは慣習に従って、アクションに対応するビューを暗黙的に呼び出す。
上記でも、renderを明示的に呼び出し、show_followという同じビューを出力している。したがって、作成が必要なビューはこれ1つですむ。

フォローしているユーザーとフォロワーの両方を表示するshow_followビュー↓

app/views/users/show_follow.html.erb
<% provide(:title, @title) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <%= gravatar_for @user %>
      <h1><%= @user.name %></h1>
      <span><%= link_to "view my profile", @user %></span>
      <span><b>Microposts:</b> <%= @user.microposts.count %></span>
    </section>
    <section class="stats">
      <%= render 'shared/stats' %>
      <% if @users.any? %>
        <div class="user_avatars">
          <% @users.each do |user| %>
            <%= link_to gravatar_for(user, size: 30), user %>
          <% end %>
        </div>
      <% end %>
    </section>
  </aside>
  <div class="col-md-8">
    <h3><%= @title %></h3>
    <% if @users.any? %>
      <ul class="users follow">
        <%= render @users %>
      </ul>
      <%= will_paginate %>
    <% end %>
  </div>
</div>

[Follow] ボタン (基本編)

フォローとフォロー解除はそれぞれリレーションシップの作成と削除に対応しているため、まずはRelationshipsコントローラが必要となる。

$ rails generate controller Relationships

次に、logged_in_userフィルターRelationshipsコントローラのアクションに対して追加する。

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
  end

  def destroy
  end
end

[Follow] / [Unfollow] ボタンを動作させるためには、フォームから送信されたパラメータを使って、followed_id に対応するユーザーを見つけてくる必要がある。

その後、見つけてきたユーザーに対して適切に follow/unfollowメソッド を使う。

Relationshipsコントローラ↓

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
    user = User.find(params[:followed_id])
    current_user.follow(user)
    redirect_to user
  end

  def destroy
    user = Relationship.find(params[:id]).followed
    current_user.unfollow(user)
    redirect_to user
  end
end

これにより、フォロー/フォロー解除の機能が完成した。

[Follow] ボタン (Ajax編)

上記ではRelationshipsコントローラの createアクションdestroyアクション を単に元のプロフィールにリダイレクトしていた。

ここで、Ajaxを使えば、Webページからサーバーに「非同期」で、ページを移動することなくリクエストを送信することができる。

WebフォームにAjaxを採用するのは今や当たり前になりつつあるので、RailsでもAjaxを簡単に実装できるようになっている。

form_for

というコードを

form_for ..., remote: true

と置き換えるだけで、Railsは自動的にAjaxを使うようになる。
Ajaxを使ったフォローフォーム↓

app/views/users/_follow.html.erb
<%= form_for(current_user.active_relationships.build, remote: true) do |f| %>
  <div><%= hidden_field_tag :followed_id, @user.id %></div>
  <%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>

Ajaxを使ったフォロー解除フォーム↓

app/views/users/_unfollow.html.erb
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
             html: { method: :delete },
             remote: true) do |f| %>
  <%= f.submit "Unfollow", class: "btn" %>
<% end %>

フォームの更新が終わったので、今度はこれに対応するRelationshipsコントローラを改造して、Ajaxリクエストに応答できるようにする。

こういったリクエストの種類によって応答を場合分けするときは、respond_toメソッドというメソッドを使うようにする。

respond_to do |format|
  format.html { redirect_to user }
  format.js
end

上の (ブロック内の) コードのうち、いずれかの1行が実行される。

RelationshipsコントローラでAjaxに対応させるために、respond_toメソッドをcreateアクションとdestroyアクションに追加する。

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    #追加修正
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    #追加修正
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end
end

ビューで変数を使うため、userが@userに変わっている

今度はブラウザ側でJavaScriptが無効になっていた場合 (Ajaxリクエストが送れない場合) でもうまく動くようにする。

config/application.rb
require File.expand_path('../boot', __FILE__)
.
.
.
module SampleApp
  class Application < Rails::Application
    .
    .
    .
    # 認証トークンをremoteフォームに埋め込む
    config.action_view.embed_authenticity_token_in_remote_forms = true
  end
end

.js.erbファイル

JavaScriptが有効になっていても、まだ十分に対応できていない部分がある。

というのも、Ajaxリクエストを受信した場合は、Railsが自動的にアクションと同じ名前を持つJavaScript用の埋め込みRuby (.js.erb) ファイル (create.js.erbやdestroy.js.erbなど) を呼び出指定しまうからである。

なのでファイルを新たに作成する必要がある。

JS-ERbファイルの内部では、DOM (Document Object Model) を使ってページを操作するため、RailsがjQuery JavaScriptヘルパーを自動的に提供している。

JavaScriptと埋め込みRubyを使ってフォローの関係性を作成する↓

app/views/relationships/create.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>");
$("#followers").html('<%= @user.followers.count %>');

Ruby JavaScript (RJS) を使ってフォローの関係性を削除する↓

app/views/relationships/destroy.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>");
$("#followers").html('<%= @user.followers.count %>');

これらのコードにより、プロフィールページを更新させずにフォローとフォロー解除ができるようになった。

ステータスフィード

現在のユーザーにフォローされているユーザーのマイクロポストの配列を作成し、現在のユーザー自身のマイクロポストと合わせて表示する。

ステータスフィードを実装するには現在のユーザーによってフォローされているユーザーに対応するユーザーidを持つマイクロポストを取り出し、同時に現在のユーザー自身のマイクロポストも一緒に取り出すようにする必要がある。

フィードに必要な3つの条件を下に示す。

・フォローしているユーザーのマイクロポストがフィードに含まれていること。
・自分自身のマイクロポストもフィードに含まれていること。
・フォローしていないユーザーのマイクロポストがフィードに含まれていないこと

最初に、このフィードで必要なクエリについて考えてみる。
ここで必要なのは、micropostsテーブル から、あるユーザー (つまり自分自身) がフォローしているユーザーに対応するidを持つマイクロポストをすべて選択 (select) することである。

このクエリを模式的に書くと次のようになる。

SELECT * FROM microposts
WHERE user_id IN (<list of ids>) OR user_id = <user id>

↑を参考に、今回必要になる選択は、上よりも少し複雑で、例えば次のような形になる。

Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)

このfollowing_idsメソッドは、has_many :followingの関連付けをしたときにActive Recordが自動生成したものである。

これにより、user.followingコレクションに対応するidを得るためには、関連付けの名前の末尾に_idsを付け足すだけで済む。

app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # パスワード再設定の期限が切れている場合はtrueを返す
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end

  # ユーザーのステータスフィードを返す
  def feed
    Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
  end

  # ユーザーをフォローする
  def follow(other_user)
    following << other_user
  end
  .
  .
  .
end

サブセレクト

問題点として上記のフィードの実装は、投稿されたマイクロポストの数が膨大になったときにうまくスケールしない、つまり、フォローしているユーザーが5,000人程度になるとWebサービス全体が遅くなる可能性がある。

上に示したコードの問題点は、following_idsでフォローしているすべてのユーザーをデータベースに問い合わせし、さらに、フォローしているユーザーの完全な配列を作るために再度データベースに問い合わせしている点である。

このような問題は、SQLのサブセレクト(subselect)を使うと解決できる。

まずはコードを若干修正し、フィードをリファクタリングすることから始める。

whereメソッド内の変数に、キーと値のペアを使う↓

app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # ユーザーのステータスフィードを返す
  def feed
    Micropost.where("user_id IN (:following_ids) OR user_id = :user_id",
     following_ids: following_ids, user_id: id)
  end
  .
  .
  .
end

前者の疑問符を使った文法も便利だが、同じ変数を複数の場所に挿入したい場合は、後者の置き換え後の文法を使う方がより便利になる。

上記ではfollowing_idsをSQLのサブセレクトとして使う。
つまり、「ユーザー1がフォローしているユーザーすべてを選択する」というSQLを既存のSQLに内包させる形になり、結果としてSQLは次のようになる。

SELECT * FROM microposts
WHERE user_id IN (SELECT followed_id FROM relationships
WHERE follower_id = 1)
OR user_id = 1

これでもっと効率的なフィードを実装する準備がきた。
フィードの最終的な実装↓

app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # ユーザーのステータスフィードを返す
  def feed
    following_ids = "SELECT followed_id FROM relationships
                     WHERE follower_id = :user_id"
    Micropost.where("user_id IN (#{following_ids})
                     OR user_id = :user_id", user_id: id)
  end
  .
  .
  .
end

(ここに記述されているコードは生のSQLを表す文字列であり、following_idsという文字列はエスケープされているのではなく、見やすさのために式展開している)

これでステータスフィードの実装は完了。

さいごに

これでrailsチュートリアルを全て完走した。
ここで出てくる知識はサービスを作る上で基本になるところだろうからしっかり復習して自分の知識にしようと思う。

ここにプラスアルファで返信機能などもつけることを演習として推奨されているのでまた挑戦してみようと思う。

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

bcryptがインストールしてるのに出来てないと出るエラーと対策

概要

Railsチュートリアルを進行中に躓いたbcryptに関するエラーと対策を記載していきます。

内容

まずは、インストール手順を説明します。

Gemfileに追加したいライブラリとそのバージョンを追加する。

Gemfile
source 'https://rubygems.org'

gem 'rails',        '5.0.0.1'

gem 'bcrypt',         '3.1.11' #追加
.
.
.

そしてコマンドを実行してインストール

$ bundle install

ここまでは、問題ない。rails test などのコマンドを実行すると
bcryptをインストールしたにも関わらず、インストールしてないと出てくる。

You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install
・
・
・

チュートリアル進行中にこのエラーで嵌る人は結構いるみたいです。

対策

色んな方のQiitaを参考にして改善に試みてみましたが、全く原因も対策も分からず
次の日になってbundle installしてrails testを実行してみると正常に動作しました...。

ただ、IDEはCloud9を使用していて、30分でサーバーが切れるので
サーバーの再起動などが関わっている可能性もあるので実行してみる価値はあると思います。

Weblickの再起動などの対策方法もあるみたいなので調べてみてください。
参考URLに自分が実行していなかった方法が記載されていたので、是非試してみてください。

参考URL

https://qiita.com/ricoirico/items/f3600abea6eb221a62df

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

もそ、プログラミングを学ぶ【Ruby & Rails復習編】〜本引数と仮引数の違いってなに?〜

相変わらずエラー問題に苦戦中の もそ。
やさしい先輩たちから「この部分はRubyの復習をしてみるといいかも」というアドバイスをもらい早速Rubyを復習しています。

すると早速、
「げっ、ここって意味がよくわからなくてスルーしたところじゃん...」
となっていたところが出てきました。
Railsを学んだのでなんとな〜く分からんでもないのですが、理解を深めるべくリベンジしました。

今日はそんなRubyの本引数と仮引数について書いていきます。

引数について

まずは引数(ひきすう)についておさらいします。

引数は、プログラムでメソッドなどに渡すことのできる値です。
変数の値には使える範囲が決まっていて、これをスコープと呼びます。
たとえば、あるdef~end内で定義した変数は、ほかのメソッドでそのまま使うことはできません

引数を使うことでメソッドの外にある変数、つまりスコープ外にある変数をメソッドの中で使うことができます。

そして引数には、メソッドを呼び出す部分に記述する本引数とメソッドを定義している部分に記述する仮引数の2つがあります。
コードを実際に見てみましょう。

 def multi(input)()内は仮引数
    puts input * input
   end

 puts "何か数字を入力してください"
   input = gets.to_i

 multi(input)()内は本引数

こんな感じです。
コードが読み込まれて処理される順番は③→②→①となります。
③と②でスコープを定義して、引数を使うことで①に変数を渡しています。

ひとつポイントなのが、仮引数の名前は本引数と違ってもいいということ。
先ほどのコードの流れは、
③のinputという本メソッド内に②gets.to_iという数値を入れて、①で結果を表示する 
というものでした。
これは方程式と同じで、仮の名前に数値を代入しているので、「x=aとして x+1*10=aを解きなさい」という数学の問題と同じような感覚です。

なので先ほどのコードは、

def multi(number)
    puts number * number
    return number * number
  end

  puts "何か数字を入力してください"
  value = gets.to_i

  multi(value)

と表すことができます。

スコープの範囲に注意

次に、このようなコードで考えてみましょう。

 def rename(name)
    name = "もそと#{name}"
   end

 name = "ポチ"
   rename(name)
   puts name

このコードを実行すると、出力結果は「ポチ」だけになります。
では、「もそとポチ」と出力するにはどうすればいいでしょうか?

このコードのポイントは、何回も出てきている"name"の記述。
同じ名前が出てきているのですが、実は①と②で中身が違います

...意味分かんないですよね。私もわからないです。

というわけで、LINEでポチに泣きつきました。

無題1160 3.jpeg

「え、これ外側で名前変えてるだけだからでしょ(即答)」

ここでポチのいう外側は②の部分。

 def rename(name)
    name = "もそと#{name}"
   end

 name = "ポチ"
   rename(name)
   puts name

②のコードでは、
「name=ポチとして、rename(ポチ)にします。ではnameを出してください」
と記述しています。
最初にname=ポチと言っているので、ここで指すnameの中身は「ポチ」になります。

では①の部分はどうでしょうか。
「rename(name)を定義します。name=”もそとポチ”です」

よくよく考えてみてください。
これ、②と①でnameで指示しているnameの中身が違いますよね。

なので出力結果を「もそとポチ」にするには、

 def rename(name)
    name = "もそと#{name}"
   end

 name = "ポチ"
   name=rename(name)
   puts name

このように記述すればよいのです!なるほど〜!

--
ふわふわしていた引数の考え方が、やっときちんと理解できた...気がします(理解したとは言っていません)
配列と本引数/仮引数はややこしいとポチも言っていたので、ここは焦らず少しずつ理解を深めていこうと思います。
果たして理解できる日は来るのか...もその修行は続く。

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

Rails Tutorialの次に8 Beautiful Ruby on Rails Apps in 30 Days & TDD 〜Udemy感想その1〜

はじめに

4月からエンジニアとして新社会人になります

内定もらってから1年くらいUdemyでプログラミング学習(主にWeb系)をしていて、
修了した講座も増えてきました
(大学での専攻は情報技術とは全く関係なく、内定頂いた時点でもバリバリ初心者でした)

メンターの方に感想をQiitaに書いてみたらと言われたので当時を思い出しながら書いていきます

同じように初心者からWeb系の勉強始める人の参考なれば嬉しいなあ

使った講座

勉強したのはこれ↓
8 Beautiful Ruby on Rails Apps in 30 Days & TDD - Immersive

railsを使ってinstagramやHacker News等のウェブアプリを8個作る

時間は計16時間、なのにまさかの無料

音声は英語、英語字幕はついてるけど自動生成なのでちょくちょく間違いアリ

ただ動画を見ながらなので言ってることが全部理解できてなくても何がしたいか、何言ってるのかはだいたいわかる

この講座に限らず、英語の講座の方が日本語の講座より内容が盛りだくさん(単純に1講座の時間が長い)なイメージ

セールになるとどちらも同じ値段になるので英語の教材選んどいた方がコスパはいいと思います

その時の自分

コードを書きはじめて1年くらい(ほぼPythonのみ、Rubyはちょっとだけ)

Rails Tutorialは最後までやった、ただ理解できないとこは飛ばした

仕事ではrailsを使うと聞いてたの、Rails Tutorialの次の教材を探してた

良かった点

  • 無料!!

無料です、ああ素晴らしい。。

Webの勉強興味はあるけどお金払ってまでやりたいかって言われるとなーって人はとりあえずやってみてください

railsの有料の講座もいくつかやってみましたが遜色ないと思います

  • たくさんのアプリが作れる

初心者あるあるかどうかはわからないんですが

僕の場合フレームワークのチュートリアルが終わった段階で

「うん、なんとなく言われた通りのものはできた。。。
で、これを使ったら何が作れるの?

ってなったんですよね

この講座は8つアプリを作るので多くの機能を触るので
「画像のアップロードはこうやるのか。。」
「動画のカテゴライズって確かによく見るな」
「星5つのレーティングも付けれるのか!」
などrailsを使ってこんな機能が実装できるんだという気づきが多くありました

せっかくrails勉強したから何か作りたい、でも何を作ったらいいかわからない
って人もこの講座で8個もアプリ作ったら何かヒントがあるかも知れません

今ひとつな点

  • rail触ったことないとキツいかも。。

これは僕の英語力が足りないのかも知れませんがrailsを全く触ったことない状態で
挑むのは厳しい印象でした

講座の最初にRuby on railsの説明、インストールの仕方、MVCの説明等もしてくれていますが
やっぱり初めてrails触る人は一旦Rails Tutorial等の日本語の教材をやった方がいいとおもいます

英語得意な方はガンガンいっちゃってください!

  • 長い。。笑

今ひとつなわけじゃないんですが、、
盛りだくさんだと褒めといてあれなんですが、やっぱり長いです笑
最後までやるにはかなり時間がかかるので余裕を持った計画で取り組むといいとおもいます
(絶対16時間の倍以上かかると思うので)
講師も30日でって言ってますしね

ただ1つ1つのアプリの作成はそんなに時間かからないので
そういう意味でも初心者には優しいです

ちなみに

この講座なんですが講師がもう古いって言ってサポートが修了してます笑
ただ講座の受講はまだできるみたいですし基本的な部分を勉強するには
初心者にはオススメできると思います

まとめ

半年ほど前に使った講座なのでうろ覚えのところもありますが
無料とは思えないほど盛りだくさんな講座でした

Rails Tutorial終わってさあ、どうしよっかなーって人はぜひやってみてください

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

数分経ってもCloud9でherokuコマンドが使えるようにする

環境

Railsチュートリアルの推奨環境

Cloud9のIDE
heroku

原因


Cloud9のIDEは30分アクセスが無いとサーバーが落ちるようになっている。

(恥ずかしながら全く知りませんでした。)

また、ターミナル毎に秘密鍵やherokuが設定されているため
サーバーを立ち上げ直したり、別ターミナルを開くと登録されていない状態に戻る

$ heroku create
bash: heroku: command not found

Railsチュートリアル 第1章 1.5.1 herokuのセットアップ で紹介されている通り
cloud9上でherokuをインストールする事が出来ますが、毎回無駄な処理が必要となる。

source <(curl -sL https://cdn.learnenough.com/heroku_install)

さらに詳しい原因については参考URLをご覧ください。

現在の自分の知識では、理解が追いつかなかったので説明が出来ません。

日々精進をして理解出来るようになりたいと思います。

対策


ターミナル起動時にPOSTが自動で設定されるようにする必要がある。
$ vi ~/.bashrc

# 以下をファイルの最後に追記

PATH=/usr/local/heroku/bin:$PATH

# こちらの記載でも可

source $HOME/.profile > /dev/null

参考URL

https://qiita.com/tathuhi10/items/281c0d8e03438e0e752c

https://railstutorial.jp/chapters/beginning?version=5.1#sec-heroku_setup

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

railsチュートリアル 第十三章

はじめに

この記事は、railsチュートリアル第十二章の行程を復習しやすいように要約したものとなっています。

ユーザーのマイクロポスト

この章では短いメッセージを投稿できるようにするためのリソースマイクロポストを追加していく。

Micropostモデル

はじめにUserモデルの関連付けを含むMicropostモデルを生成する。

Micropostモデルは、マイクロポストの内容を保存するcontent属性と、特定のユーザーとマイクロポストを関連付けるuser_id属性の2つの属性だけを持つ。

$ rails generate model Micropost content:text user:references

ある程度の量のテキストを格納する場合はtext型が望ましいのでcontentにはtext型をあてている。

上記のコマンドを実行するとMicropostモデルが生成され、ユーザーと1対1の関係であることを表すbelongs_toのコードも追加されている。

これはuser:referencesという引数を含めたことにより、自動的にインデックスと外部キー参照付きのuser_idカラムが追加され、UserとMicropostを関連付けする下準備をしてくれるから。

app/models/micropost.rb
class Micropost < ApplicationRecord
   belongs_to :user
end

またUserモデルのときと同じで、Micropostモデルのマイグレーションファイルでもt.timestampsという行 (マジックカラム) が自動的に生成されている。

db/migrate/[timestamp]_create_microposts.rb
class CreateMicroposts < ActiveRecord::Migration[5.0]
  def change
    create_table :microposts do |t|
      t.text :content
      t.references :user, foreign_key: true

      t.timestamps
    end
    #インデックスを付与
    add_index :microposts, [:user_id, :created_at]
  end
end

上のファイルではuser_idとcreated_atカラムにインデックスが付与されている。

こうすることで、user_idに関連付けられたすべてのマイクロポストを作成時刻の逆順で取り出しやすく*なる。

ここでいつも通りマイグレーションで、データベースを更新しておく。

Micropostのバリデーション

以下のようにバリデーションで存在性、最大文字を検証する。

app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end

User/Micropostの関連付け

Webアプリケーション用のデータモデルを構築するにあたって、個々のモデル間での関連付けを十分考えておくことが重要となる。

今回の場合は、それぞれのマイクロポストは1人のユーザーと関連付けられ、それぞれのユーザーは (潜在的に) 複数のマイクロポストと関連付けられる。

user/micropost関連メソッドのまとめは以下のようになる。

メソッド 用途
micropost.user Micropostに紐付いたUserオブジェクトを返す
user.microposts Userのマイクロポストの集合をかえす
user.microposts.create(arg) userに紐付いたマイクロポストを作成する
user.microposts.create!(arg) userに紐付いたマイクロポストを作成する (失敗時に例外を発生)
user.microposts.build(arg) userに紐付いた新しいMicropostオブジェクトを返す
user.microposts.find_by(id: 1) userに紐付いていて、idが1であるマイクロポストを検索する

@user.microposts.buildのようなコードを使うためには、 UserモデルとMicropostモデルをそれぞれ更新して、関連付ける必要がある。

app/models/micropost.rb
class Micropost < ApplicationRecord
  #マイクロポストがユーザーに所属する関連付け
  belongs_to :user
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end
app/models/user.rb
class User < ApplicationRecord
 #ユーザーがマイクロポストを複数所有する (has_many) 関連付け
 has_many :microposts
  .
  .
  .
end

マイクロポストを改良する

この項では、具体的には、ユーザーのマイクロポストを特定の順序で取得できるようにしたり、マイクロポストをユーザーに依存させて、ユーザーが削除されたらマイクロポストも自動的に削除されるようにしていく。

デフォルトのスコープ

user.micropostsメソッドはデフォルトでは読み出しの順序に対して何も保証しないが、ブログやTwitterの慣習に従って、作成時間の逆順、つまり最も新しいマイクロポストを最初に表示するように改良する。

これを実装するためには、default scopeというテクニックを使用する。

投稿はSQL文で、
order('created_at DESC')
を引数に与えたい。

よって以下のようになる。

app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  #追加
  default_scope -> { order(created_at: :desc) }
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end

Dependent: destroy

サイト管理者はユーザーを破棄する権限を持つ。ユーザーが破棄された場合、ユーザーのマイクロポストも同様に破棄されるようにしたい。

この振る舞いは、has_manyメソッドオプションを渡してあげることで実装できる。

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  .
  .
  .
end

dependent: :destroyというオプションを使うと、ユーザーが削除されたときに、そのユーザーに紐付いた (そのユーザーが投稿した) マイクロポストも一緒に削除されるようになる。

マイクロポストを表示する

Web経由でマイクロポストを作成する方法は現時点ではないが、ここでは、Twitterのような独立したマイクロポストのindexページは作らずに、ユーザーのshowページで直接マイクロポストを表示させることにする。

マイクロポストの描画

ここで、ユーザーのプロフィール画面 (show.html.erb) でそのユーザーのマイクロポストを表示させたり、これまでに投稿した総数も表示させたりしていく。

まずは、Micropostのコントローラとビューを作成するために、コントローラを生成する。

$ rails generate controller Microposts

以前は_user.html.erbパーシャルを使って自動的に@users変数内のそれぞれのユーザーを出力していた。

これを参考に、_micropost.html.erbパーシャルを使ってマイクロポストのコレクションを表示しようとすると、次のようになる。

<ol class="microposts">
  <%= render @microposts %>
</ol>

よって対応するパーシャルを以下に示す。

app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content"><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
  </span>
</li>

上記では一度にすべてのマイクロポストが表示されてしまう潜在的問題に対処している。

前回のユーザー一覧では
<%= will_paginate %>
のようにコードは引数なしで動作していた。
これはwill_paginateが、Usersコントローラのコンテキストにおいて、@usersインスタンス変数が存在していることを前提としているためとなる。

このインスタンス変数はActiveRecord::Relationクラスのインスタンスで、今回の場合はUsersコントローラのコンテキストからマイクロポストをページネーションしたいため (つまりコンテキストが異なるため)、明示的に@microposts変数をwill_paginateに渡す必要がある。

したがって、そのようなインスタンス変数をUsersコントローラのshowアクションで定義しなければならない。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def show
    @user = User.find(params[:id])
    #追加
    @microposts = @user.microposts.paginate(page: params[:page])
  end
  .
  .
  .
end

マイクロポストの投稿数

投稿数の表示はcountメソッドを使うことで可能となる。
user.microposts.count

以上のことを踏まえてプロフィール画面にマイクロポストを表示させてみる。

app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
    </section>
  </aside>
  #追加
  <div class="col-md-8">
    <% if @user.microposts.any? %>
      <h3>Microposts (<%= @user.microposts.count %>)</h3>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>

マイクロポストのサンプル

現時点ではマイクロポストがない状態なので、サンプルを追加する。

すべてのユーザーにマイクロポストを追加しようとすると時間が掛かり過ぎるので、takeメソッドを使って最初の6人だけに追加することにする。
User.order(:created_at).take(6)

この6人については、1ページの表示限界数 (30) を越えさせるために、それぞれ50個分のマイクロポストを追加するようにする。

db/seeds.rb
.
.
.
users = User.order(:created_at).take(6)
50.times do
  content = Faker::Lorem.sentence(5)
  users.each { |user| user.microposts.create!(content: content) }
end
$ rails db:migrate:reset
$ rails db:seed

このコマンドをうち、railsサーバーを再起動するとプロフィールにマイクロポストのサンプルが表示される。

ここに CSSを加えてスタイルを整える必要があるが、ここでは省略。

マイクロポストを操作する

データモデリングとマイクロポスト表示テンプレートの両方が完成したので、次はWeb経由でそれらを作成するためのインターフェイスを整えていく。

従来のRails開発の慣習と異なり、Micropostsリソースへのインターフェイスは、主にプロフィールページとHomeページのコントローラを経由して実行されるから、Micropostsコントローラにはneweditのようなアクションは不要でcreatedestroyがあれば十分ということになる。

したがって、Micropostsのリソースは以下になる。

config/routes.rb
Rails.application.routes.draw do
  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  resources :users
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  #追加
  resources :microposts,          only: [:create, :destroy]
end
HTTPリクエスト URL アクション 名前付きルート
POST /microposts create microposts_path
DELETE /microposts/1 destroy micropost_path(micropost)

マイクロポストのアクセス制御

関連付けられたユーザーを通してマイクロポストにアクセスするので、createアクションやdestroyアクションを利用するユーザーは、ログイン済みでなければいけない。

以前の章では、beforeフィルターのlogged_in_userメソッドを使って、ログインを要求した。

あのときはUsersコントローラ内にこのメソッドがあったので、beforeフィルターで指定していたが、このメソッドはMicropostsコントローラでも必要となるので、このメソッドをApplicationコントローラに移す。

ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper

  private

    # ユーザーのログインを確認する
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
end

コードが重複しないよう、このときUsersコントローラからlogged_in_userを削除しておく。

マイクロポストを作成する

logged_in_userメソッドにより、createアクションやdestroyアクションに対するアクセス制限が、beforeフィルターで簡単に実装できるようになった。

createアクションを定義していく。

app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      render 'static_pages/home'
    end
  end

  def destroy
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
end

上記ではmicropost_paramsでStrong Parametersを使っていることにより、マイクロポストのcontent属性だけがWeb経由で変更可能になっている。

次にマイクロポスト作成フォームを構築するために、サイト訪問者がログインしているかどうかに応じて異なるHTMLを提供するコードを使う。

app/views/static_pages/home.html.erb
#追加
<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
  </div>
<% else %>

  <div class="center jumbotron">
    <h1>Welcome to the Sample App</h1>

    <h2>
      This is the home page for the
      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </h2>

    <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
  </div>

  <%= link_to image_tag("rails.png", alt: "Rails logo"),
              'http://rubyonrails.org/' %>
#追加
<% end %>

上のコードを動かすにはいくつかのパーシャルを作る必要がある。

サイドバーで表示するユーザー情報のパーシャル

app/views/shared/_user_info.html.erb
<%= link_to gravatar_for(current_user, size: 50), current_user %>
<h1><%= current_user.name %></h1>
<span><%= link_to "view my profile", current_user %></span>
<span><%= pluralize(current_user.microposts.count, "micropost") %></span>

↑ではユーザーが投稿したマイクロポストの総数が表示されるようになっている。

pluralizeメソッドを使って “1 micropost” や “2 microposts” と表示するように調整している。

マイクロポスト投稿フォームのパーシャル

app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
<% end %>

上記のフォームを動かすには、2箇所の変更が必要となる。

一つ目はhomeアクションに@micropostを定義すること。

app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
    @micropost = current_user.microposts.build if logged_in?
  end

  def help
  end

  def about
  end

  def contact
  end
end

current_userメソッドはユーザーがログインしているときしか使えないから、@micropost変数もログインしているときのみ定義されるようになる。

もう一つの変更はエラーメッセージのパーシャルを再定義すること。

前回はエラーメッセージパーシャルが@user変数を直接参照していたが今回は代わりに@micropost変数を使う必要がある。

これらのケースをまとめると、フォーム変数fをf.objectとすることによって、関連付けられたオブジェクトにアクセスすることができる。

app/views/shared/_error_messages.html.erb
<% if object.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(object.errors.count, "error") %>.
    </div>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

この修正により、ユーザー登録・パスワード再設定・ユーザー編集のそれぞれのビューも変更する必要がある。

app/views/users/new.html.erb
  <%= render 'shared/error_messages', object: f.object %>
app/views/users/edit.html.erb
<%= render 'shared/error_messages', object: f.object %>
app/views/password_resets/edit.html.erb
 <%= render 'shared/error_messages', object: f.object %>

これによりマイクロポスト投稿フォームが動くようになる。

フィードの原型

Homeページにまだマイクロポストを表示する部分が実装されていないから今の段階では投稿した内容をすぐに見ることができない。

ユーザー自身のポストを含むマイクロポストのフィードがないと不便となるのでfeedメソッドをUserモデルで作る。

フィードの原型では、まずは現在ログインしているユーザーのマイクロポストをすべて取得する。

app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # 試作feedの定義
  # 完全な実装は次章の「ユーザーをフォローする」を参照
  def feed
    Micropost.where("user_id = ?", id)
  end

    private
    .
    .
    .
end

↑のコードは↓のコードと本質的に同等となるが、↑の方が応用が効きやすいため↑を採用する。

def feed
  microposts
end

サンプルアプリケーションでフィードを使うために、現在のユーザーのページ分割されたフィードに@feed_itemsインスタンス変数を追加し、次にフィード用のパーシャルをHomeページに追加する。

app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
    if logged_in?
      @micropost  = current_user.microposts.build
      #追加
      @feed_items = current_user.feed.paginate(page: params[:page])
    end
  end

  def help
  end

  def about
  end

  def contact
  end
end
app/views/shared/_feed.html.erb
<% if @feed_items.any? %>
  <ol class="microposts">
    <%= render @feed_items %>
  </ol>
  <%= will_paginate @feed_items %>
<% end %>

後は、いつものようにフィードパーシャルを表示すればHomeページにフィードを追加できる。

app/views/static_pages/home.html.erb
<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3>Micropost Feed</h3>
      #追加
      <%= render 'shared/feed' %>
    </div>
  </div>
<% else %>
  .
  .
  .
<% end %>

これでマイクロポストの作成はうまくいくようになった。

ただマイクロポストの投稿が失敗すると、 Homeページは@feed_itemsインスタンス変数を期待しているため、現状では壊れてしまう。

よって空の配列を渡しておく。

app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      #追加
      @feed_items = []
      render 'static_pages/home'
    end
  end

  def destroy
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
end

マイクロポストを削除する

最後の機能として、マイクロポストリソースにポストを削除する機能を追加する。

これはユーザー削除と同様に、"delete" リンクを使用する。

ユーザーの削除は管理者ユーザーのみが行えるように制限されていたのに対し、今回は自分が投稿したマイクロポストに対してのみ削除リンクが動作する。

最初のステップとして、マイクロポストのパーシャルに削除リンクを追加。

app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content"><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      #追加
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
  </span>
</li>

次に、Micropostsコントローラのdestroyアクションを定義する。

app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
  before_action :correct_user,   only: :destroy
  .
  .
  .
  def destroy
    @micropost.destroy
    flash[:success] = "Micropost deleted"
    redirect_to request.referrer || root_url
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end

    def correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
end

上記ではrequest.referrerというメソッドを使っている。

このメソッドはフレンドリーフォワーディングのrequest.url変数と似ていて、一つ前のURLを返す。(今回の場合、Homeページになる)

ちなみに、元に戻すURLが見つからなかった場合でも、||演算子でroot_urlをデフォルトに設定している。

マイクロポストの画像投稿

マイクロポストの基本はこれで実装できた。
これにプラスで画像投稿もできるようにする。

投稿した画像を扱ったり、その画像をMicropostモデルと関連付けするために、今回はCarrierWaveという画像アップローダーを使う。

まずはcarrierwave gemをGemfileに追加し、mini_magick gemとfog gemsも含めて追加しておく。

これらのgemは画像をリサイズしたり、本番環境で画像をアップロードするために使う。

source 'https://rubygems.org'
.
.
gem 'carrierwave',             '1.2.2'
gem 'mini_magick',             '4.7.0'
.
.
group :production do
  gem 'fog', '1.42'
.

次に、いつものようにbundle installを実行する。

$ bundle install

CarrierWaveを導入すると、Railsのジェネレーターで画像アップローダーが生成できるようになる。

$ rails generate uploader Picture

次に必要となるpicture属性をMicropostモデルに追加するために、マイグレーションファイルを生成し、開発環境のデータベースに適用させる。

$ rails generate migration add_picture_to_microposts picture:string
$ rails db:migrate

CarrierWaveに画像と関連付けたモデルを伝えるためには、mount_uploaderというメソッドを使う。

このメソッドは、引数に属性名のシンボルと生成されたアップローダーのクラス名を取る。

app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  #追加
  mount_uploader :picture, PictureUploader
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end

システムによっては、ここで一旦Railsサーバーを再起動させる必要がある。

マイクロポスト投稿フォームに画像アップローダーを追加するにはfile_fieldタグを含める必要があり、以下のようにする。

app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
  <span class="picture">
    #追加
    <%= f.file_field :picture %>
  </span>
<% end %>

最後に、Webから更新できる許可リストにpicture属性を追加する。
追加すると、micropost_paramsメソッドは次の通り。

app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
  before_action :correct_user,   only: :destroy
  .
  .
  .
  private

    def micropost_params
      params.require(:micropost).permit(:content, :picture)
    end

    def correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
end

一度画像がアップロードされれば、Micropostパーシャルのimage_tagヘルパーでその画像を描画できるようになる。

また、画像の無い (テキストのみの) マイクロポストでは画像を表示させないようにするために、picture?という論理値を返すメソッドを使う。

このメソッドは、画像用の属性名に応じて、CarrierWaveが自動的に生成してくれるメソッドとなる。

app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
    <%= micropost.content %>
    #追加
    <%= image_tag micropost.picture.url if micropost.picture? %>
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
  </span>
</li>

画像の検証

上記のままではアップロードされた画像に対する制限がないため、もしユーザーが巨大なファイルを上げたり、無効なファイルを上げると問題が発生してしまう。

この欠点を直すために、画像サイズやフォーマットに対するバリデーションを実装し、サーバー用とクライアント (ブラウザ) 用の両方に追加するようにする。

最初のバリデーションでは、有効な画像の種類を制限していくが、これはCarrierWaveのアップローダーの中に既にヒントがある。

生成されたアップローダーの中にコメントアウトされたコードがありますが、ここのコメントアウトを取り消すことで、画像のファイル名から有効な拡張子 (PNG/GIF/JPEGなど) を検証することができるようになる。

app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
  storage :file

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先  
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

2つ目のバリデーションでは、画像のサイズを制御する。

これはMicropostモデルに書き足してく。
先ほどのバリデーションとは異なり、ファイルサイズに対するバリデーションはRailsの既存のオプション (presenceやlengthなど) にはない。
したがって、今回は手動でpicture_sizeという独自のバリデーションを定義する。

app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  mount_uploader :picture, PictureUploader
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
  #追加 
  validate  :picture_size

  private

    # アップロードされた画像のサイズをバリデーションする
    def picture_size
      if picture.size > 5.megabytes
        errors.add(:picture, "should be less than 5MB")
      end
    end
end

このvalidateメソッドでは、引数にシンボル (:picture_size) を取り、そのシンボル名に対応したメソッドを呼びだす。

次に上で定義したバリデーションをビューに読み込むために、クライアント側に2つの処理を追加する。

まずfile_fieldタグにacceptパラメータを付与し、大きすぎるファイルサイズに対して警告を出すために、ちょっとしたJavaScript(jQuery)を書き加えることでバリデーションが動くようになる。

app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
  <span class="picture">
    #追加
    <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
  </span>
<% end %>
#追加
<script type="text/javascript">
  $('#micropost_picture').bind('change', function() {
    var size_in_megabytes = this.files[0].size/1024/1024;
    if (size_in_megabytes > 5) {
      alert('Maximum file size is 5MB. Please choose a smaller file.');
    }
  });
</script>

###画像のリサイズ

ファイルサイズに対するバリデーションはできたから、さいごに画像サイズ (縦横の長さ) に対する制限を追加する。

画像をリサイズするためには、画像を操作するプログラムが必要になる。
今回はImageMagickというプログラムを使うので、これを開発環境にインストールする。

$ sudo yum install -y ImageMagick

次に、MiniMagickというImageMagickとRubyを繋ぐgemを使って、画像をリサイズを試みる。

今回はresize_to_limit: [400, 400]という方法を使用しこれは、縦横どちらかが400pxを超えていた場合、適切なサイズに縮小するオプションとなる。

app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
  #追加
  include CarrierWave::MiniMagick
  process resize_to_limit: [400, 400]

  storage :file

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先  
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

これで画像に対する制御もすみ、マイクロポスト機能を実装することができた。

さいごに

今回はマイクロポスト機能の実装がメインとなりプラスアルファで画像投稿機能を実装できた。

残るは14章のフォロー機能だけとなる。

次↓
https://qiita.com/jonnyjonnyj1397/items/91c50bb5ac1d48bc29d3

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

14日目:Scaffoldで作成したサイトにgem devise等を組み合わせていく

今週からは、scaffoldで作成した大学データと、gemのdevise、Bootstrap等を組み合わせる。

やった事

  • Railsの命名規則(単数形と複数形)
  • DBのカラム定義を後から変更
  • render partial: 部分テンプレの参照
  • validation
  • UNSIGNEDという型が存在しないPostgreSQL

使用環境

  • ホストOS: Windows10 Home
  • 仮想環境: Ubuntu Bento/Bionic
  • Ruby:2.51
  • Rails: 5.2.2
    • gem 'devise' : ログイン等の機能用
    • gem 'kaminari' : ページネーション
  • DB: PostgreSQL

Railsの命名規則(単数形と複数形)

rails gコマンドで、controller名やmodel名を指定する際に、混乱した。

# rails generate scaffold model名の単数形 フィールド名の型と並び
# rails g controller controller名の複数形
# カラムの追加
# rails generate migration AddカラムToモデル名の複数形 フィールド名と並び
  • model名は単数形で、頭文字を大文字にする
    • scaffoldの場合、modelが基準
    • modelは設計書であり、(テーブル1つに付き)1つなため
  • controller名は複数形で、頭文字を大文字にする。
    • 1つのcontrollerに複数のactionが含まれるため

DBのカラム定義を後から変更

rails g scaffoldコマンド時に、ClubStudentの外部キーの定義をreferecesとミスタイプしていた。

修正方法:app/db/migrate下のファイルを修正

app/db/migrate/20190326030303_create_club_students.rb
class CreateClubStudents < ActiveRecord::Migration[5.2]
  def change
    create_table :club_students do |t|
     #スペリングミス
     #t.refereces :student
     #訂正分
      t.references :student
      t.references :club, foreign_key: true
      t.timestamps
    end
  end
end

なお、ALTTER TABLEコマンドを使って、あとから修正する方法は
DB内のデータを書き換えるだけで、アプリ自体のファイル等は編集されない。

mysql
# ALTER TABLE テーブル名 MODIFY COLUMN カラム名 新しい定義
ALTER TABLE ClubStudent MODIFY COLUMN student references

つまり、原因の根本的な部分を修正できないので、駄目

render partial: 部分テンプレの参照

render レンダリング(render) - railsドキュメント

全てのページのヘッダー(上部)に、ログアウトや他のstudentやclubs等のリンクを乗せる
tempsnip.jpg

共通して表示させるので、/app/views/layouts/application.html.erb を編集する。
なお、部分テンプレファイル名は『_』アンダーバー始まり

/app/views/layouts/application.html.erb
<body>
# <%= render :partial => '部分テンプレ名' %>
  <%= render :partial => 'shared/header' %>
</body>

表示させたいリンクを書きこむ。

/app/views/shared/_header.html.erb
<%= link_to 'Student list', students_path %> 
<%= link_to 'subjects list', subjects_path %> 
<%= link_to 'clubs list', clubs_path %> 
<%= link_to 'exam_result list', exam_results_path %>
<%= link_to 'club_stdent list', club_students_path %>
<%= link_to 'Log Out', destroy_student_session_path, method: :delete %>

validation

参考リンク Active Record Validations
バリデーションは有効なデータだけをDBに保存するのを確実にするための最善策。

今回の実装先:clubの新規作成ページ
newclub_form.JPG

validate条件

空でないこと

validates :name, presence: true
# 因みに、空が条件ならば
# validates :name, absence: true

入力文字の長さ

文字の最大長は、データ型を要参照。varcharなら255文字まで

/app/models/club.rb
# 2文字以上
validates :name, length:{minimum:2}
# 255文字以上
validates :name, length:{maximum:255}

exclusion含まない

授業ではしなかったが、クラブ名末尾に『部』を入れない、という条件を追加してみる

/app/models/club.rb
validates :name, exclusion: { in: %w(部 サークル) }
# 『含む』ならinclusion

実装結果

空白や文字列長、『サークル』という語には、validatesが発動した
validate-clubnew.JPG

ただ、現状だと、『テニスサークル』の様に文字列と連結すると、validateが動かない
あとまわし

Club was successfully updated.

Name: サークル部

type "unsigned" does not exist (※Postgresql)

validatesの実装していく最中に、エラーに気づいた

studentのeditページで更新すると、

ActiveRecord::StatementInvalid in StudentsController#show

PG::UndefinedObject: ERROR: type "unsigned" does not exist LINE 1: ...id as subject_id, CAST(AVG(exam_results.score) as unsigned) ... ^ : SELECT subjects.id as subject_id, CAST(AVG(exam_results.score) as unsigned) as avg_score, MAX(exam_results.score) as max_score, MIN(exam_results.score) as min_score FROM "students" INNER JOIN "exam_results" ON "exam_results"."student_id" = "students"."id" INNER JOIN "subjects" ON "subjects"."id" = "exam_results"."subject_id" GROUP BY subjects.id ORDER BY subjects.id

と、エラーを吐き、因みに、ブラウザの戻るボタンで戻ると、更新されている。
また、エラー原因であると思わる、StudentController#showは

app/controllers/students_controller.rb#show
def show
    @students = Student.joins(:subjects)
                       .select('students.name, students.email, students.age, students.gender, students.opinion, subjects.id as subject_id')
                       .select('exam_results.name as exam_result_name, subjects.name as subject_name, exam_results.score')
                       .select('CAST((exam_results.score / subjects.max_score) * 100 as unsigned) as ratio')
                       .where(id: params[:id])

    avg_result = Student.joins(:subjects)
                        .select('subjects.id as subject_id')
                        .select('CAST(AVG(exam_results.score) as unsigned) as avg_score')
                        .select('MAX(exam_results.score) as max_score')
                        .select('MIN(exam_results.score) as min_score')
                        .group('subjects.id')
                        .order('subjects.id')
(以下略)

因みに、このcontrollerは、以前の大学データのcontrollerからコピーしてきたものだ。
つまり、MySQLで動くアプリのcontroller。

unsigned (MySQL)

  • MySQLにおいては正と負の整数を扱うことができる。
  • unsignedを指定すると、正の数しか格納できなくなり、代わりに範囲が2倍になる。
  • unsignedにした値が負になると、エラーを起こす
    • UNSIGNEDは、マイナス値が入らないだけでなく、マイナスになる計算もできない。
    • CASTで一時的に型を変える事で回避は可能。

Postgresqlにはunsined型は存在しない(最重要)

対応策

まだ、試験結果のデータを入れてないので、功を奏すか分からないけれども

  • unsignedをint等の型に置き換える
    • 今回は試験点数を扱っていて、intで事足りると思われる。
    • ただ、MySQLでint unsignedだと、範囲が正の方向に2倍になっている。
    • 扱う数によっては、intより1つ上ののbigintに変える必要がある
  • CAST as unsignedの部分を消す
    • MySQLでCAST as unsingedは、一時的に型を指定している

前回の大学データに倣って、今回はcast as intに変更した

app/controllers/students_controller.rb
# (該当部分だけ抜き出し)
.select('CAST((exam_results.score / subjects.max_score) * 100 as int) as ratio')

.select('CAST(AVG(exam_results.score) as int) as avg_score') 

正常に、studentデータのedit、updateが機能した。

データ入力にはpassword情報が必要

deviseの関係上、パスワード情報入りのデータでないと、コンソールから入力できない。

passwordカラムの追加

deviseのモデル等がある、Studentテーブルに、パスワードカラムを追加した。

terminal
# rails generate migration AddカラムToモデル名の複数形 フィールド名と並び
rails g migration AddPasswordToStudents password:string

db/migrate下にファイルが生成される

/db/migrate/20190327144825_add_password_to_students.rb
class AddPasswordToStudents < ActiveRecord::Migration[5.2]
  def change
    add_column :students, :password, :integer
  end
end

これで、パスワード情報入りの生徒データをDBに入力できる。

データ入力

未だデータの無い、生徒データと試験結果データをコンソールで入力した。

console
(1..100).each do |num|
  if num % 2 == 0 && num % 3 ==0
    gen = 0
    ag = 1
    elsif num % 2 == 0
    gen = rand(2)
    ag = rand(3)
  else
    gen = 1
    ag = 0
  end
  op = (1..10).map{('あ'..'わ').to_a[rand(26)]}.join
  nm = (1..3).map{('あ'..'わ').to_a[rand(26)]}.join

  user = Student.create!(name: "#{nm}", email: "#{nm}-#{rand(98)}@gmail.com", gender: gen, age: ag, opinion: op,password: 'password')
  end
console
  (1..100).each do |i|
  student = Student.find(i)
  1.upto(rand(0..4)) do
    student.clubs << Club.find(rand(1..14))
    student.save
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

大学データとログイン機能gem deviseを組み合わせる

ここ3週間の一区切りとして、今週は
scaffoldで作成した大学データと、gemのdevise、Bootstrap等を組み合わせる。

前回までのdeviseを使ったサイトに、大学データを一部コピーして、作成する予定だったが、
Active Record等がエラーを起こしたため、今回はrails newから作成した。
やってることの多くは以前のと重複しているので、詰まった所、初めてな所を、今日は書く。

やった事

  • #Railsの命名規則(単数形と複数形)
  • DBのカラム定義を後から変更
  • render partial: 部分テンプレの参照
  • validation
  • Postgresqlにはunsined型は存在しない

使用環境

  • ホストOS: Windows10 Home
  • 仮想環境: Ubuntu Bento/Bionic
  • Ruby:2.51
  • Rails: 5.2.2
    • gem 'devise' : ログイン等の機能用
    • gem 'kaminari' : ページネーション
  • DB: PostgreSQL

Railsの命名規則(単数形と複数形)

rails gコマンドで、controller名やmodel名を指定する際に、混乱した。

# rails generate scaffold model名の単数形 フィールド名の型と並び
# rails g controller controller名の複数形
# カラムの追加
# rails generate migration AddカラムToモデル名の複数形 フィールド名と並び
  • model名は単数形で、頭文字を大文字にする
    • scaffoldの場合、modelが基準
    • modelは設計書であり、(テーブル1つに付き)1つなため
  • controller名は複数形で、頭文字を大文字にする。
    • 1つのcontrollerに複数のactionが含まれるため

DBのカラム定義を後から変更

rails g scaffoldコマンド時に、ClubStudentの外部キーの定義をreferecesとミスタイプしていた。

修正方法:app/db/migrate下のファイルを修正

app/db/migrate/20190326030303_create_club_students.rb
class CreateClubStudents < ActiveRecord::Migration[5.2]
  def change
    create_table :club_students do |t|
     #スペリングミス
     #t.refereces :student
     #訂正分
      t.references :student
      t.references :club, foreign_key: true
      t.timestamps
    end
  end
end

なお、ALTTER TABLEコマンドを使って、あとから修正する方法は
DB内のデータを書き換えるだけで、アプリ自体のファイル等は編集されない。

mysql
# ALTER TABLE テーブル名 MODIFY COLUMN カラム名 新しい定義
ALTER TABLE ClubStudent MODIFY COLUMN student references

つまり、原因の根本的な部分を修正できないので、駄目

render partial: 部分テンプレの参照

render レンダリング(render) - railsドキュメント

全てのページのヘッダー(上部)に、ログアウトや他のstudentやclubs等のリンクを乗せる
tempsnip.jpg

共通して表示させるので、/app/views/layouts/application.html.erb を編集する。
なお、部分テンプレファイル名は『_』アンダーバー始まり

/app/views/layouts/application.html.erb
<body>
# <%= render :partial => '部分テンプレ名' %>
  <%= render :partial => 'shared/header' %>
</body>

表示させたいリンクを書きこむ。

/app/views/shared/_header.html.erb
<%= link_to 'Student list', students_path %> 
<%= link_to 'subjects list', subjects_path %> 
<%= link_to 'clubs list', clubs_path %> 
<%= link_to 'exam_result list', exam_results_path %>
<%= link_to 'club_stdent list', club_students_path %>
<%= link_to 'Log Out', destroy_student_session_path, method: :delete %>

validation

参考リンク Active Record Validations
バリデーションは有効なデータだけをDBに保存するのを確実にするための最善策。

今回の実装先:clubの新規作成ページ
newclub_form.JPG

validate条件

空でないこと

validates :name, presence: true
# 因みに、空が条件ならば
# validates :name, absence: true

入力文字の長さ

文字の最大長は、データ型を要参照。varcharなら255文字まで

/app/models/club.rb
# 2文字以上
validates :name, length:{minimum:2}
# 255文字以上
validates :name, length:{maximum:255}

exclusion含まない

授業ではしなかったが、クラブ名末尾に『部』を入れない、という条件を追加してみる

/app/models/club.rb
validates :name, exclusion: { in: %w(部 サークル) }
# 『含む』ならinclusion

実装結果

空白や文字列長、『サークル』という語には、validatesが発動した
validate-clubnew.JPG

ただ、現状だと、『テニスサークル』の様に文字列と連結すると、validateが動かない
あとまわし

Club was successfully updated.

Name: サークル部

type "unsigned" does not exist (※Postgresql)

validatesの実装していく最中に、エラーに気づいた

studentのeditページで更新すると、

ActiveRecord::StatementInvalid in StudentsController#show

PG::UndefinedObject: ERROR: type "unsigned" does not exist LINE 1: ...id as subject_id, CAST(AVG(exam_results.score) as unsigned) ... ^ : SELECT subjects.id as subject_id, CAST(AVG(exam_results.score) as unsigned) as avg_score, MAX(exam_results.score) as max_score, MIN(exam_results.score) as min_score FROM "students" INNER JOIN "exam_results" ON "exam_results"."student_id" = "students"."id" INNER JOIN "subjects" ON "subjects"."id" = "exam_results"."subject_id" GROUP BY subjects.id ORDER BY subjects.id

と、エラーを吐き、因みに、ブラウザの戻るボタンで戻ると、更新されている。
また、エラー原因であると思わる、StudentController#showは

app/controllers/students_controller.rb#show
def show
    @students = Student.joins(:subjects)
                       .select('students.name, students.email, students.age, students.gender, students.opinion, subjects.id as subject_id')
                       .select('exam_results.name as exam_result_name, subjects.name as subject_name, exam_results.score')
                       .select('CAST((exam_results.score / subjects.max_score) * 100 as unsigned) as ratio')
                       .where(id: params[:id])

    avg_result = Student.joins(:subjects)
                        .select('subjects.id as subject_id')
                        .select('CAST(AVG(exam_results.score) as unsigned) as avg_score')
                        .select('MAX(exam_results.score) as max_score')
                        .select('MIN(exam_results.score) as min_score')
                        .group('subjects.id')
                        .order('subjects.id')
(以下略)

因みに、このcontrollerは、以前の大学データのcontrollerからコピーしてきたものだ。
つまり、MySQLで動くアプリのcontroller。

unsigned (MySQL)

  • MySQLにおいては正と負の整数を扱うことができる。
  • unsignedを指定すると、正の数しか格納できなくなり、代わりに範囲が2倍になる。
  • unsignedにした値が負になると、エラーを起こす
    • UNSIGNEDは、マイナス値が入らないだけでなく、マイナスになる計算もできない。
    • CASTで一時的に型を変える事で回避は可能。

Postgresqlにはunsined型は存在しない(最重要)

対応策

まだ、試験結果のデータを入れてないので、功を奏すか分からないけれども

  • unsignedをint等の型に置き換える
    • 今回は試験点数を扱っていて、intで事足りると思われる。
    • ただ、MySQLでint unsignedだと、範囲が正の方向に2倍になっている。
    • 扱う数によっては、intより1つ上ののbigintに変える必要がある
  • CAST as unsignedの部分を消す
    • MySQLでCAST as unsingedは、一時的に型を指定している

前回の大学データに倣って、今回はcast as intに変更した

app/controllers/students_controller.rb
# (該当部分だけ抜き出し)
.select('CAST((exam_results.score / subjects.max_score) * 100 as int) as ratio')

.select('CAST(AVG(exam_results.score) as int) as avg_score') 

正常に、studentデータのedit、updateが機能した。

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

Scaffoldで作成したサイトにgem devise等を組み合わせていく

今週からは、scaffoldで作成した大学データと、gemのdevise、Bootstrap等を組み合わせる。

やった事

  • Railsの命名規則(単数形と複数形)
  • DBのカラム定義を後から変更
  • render partial: 部分テンプレの参照
  • validation
  • UNSIGNEDという型が存在しないPostgreSQL

使用環境

  • ホストOS: Windows10 Home
  • 仮想環境: Ubuntu Bento/Bionic
  • Ruby:2.51
  • Rails: 5.2.2
    • gem 'devise' : ログイン等の機能用
    • gem 'kaminari' : ページネーション
  • DB: PostgreSQL

Railsの命名規則(単数形と複数形)

rails gコマンドで、controller名やmodel名を指定する際に、混乱した。

# rails generate scaffold model名の単数形 フィールド名の型と並び
# rails g controller controller名の複数形
# カラムの追加
# rails generate migration AddカラムToモデル名の複数形 フィールド名と並び
  • model名は単数形で、頭文字を大文字にする
    • scaffoldの場合、modelが基準
    • modelは設計書であり、(テーブル1つに付き)1つなため
  • controller名は複数形で、頭文字を大文字にする。
    • 1つのcontrollerに複数のactionが含まれるため

DBのカラム定義を後から変更

rails g scaffoldコマンド時に、ClubStudentの外部キーの定義をreferecesとミスタイプしていた。

修正方法:app/db/migrate下のファイルを修正

app/db/migrate/20190326030303_create_club_students.rb
class CreateClubStudents < ActiveRecord::Migration[5.2]
  def change
    create_table :club_students do |t|
     #スペリングミス
     #t.refereces :student
     #訂正分
      t.references :student
      t.references :club, foreign_key: true
      t.timestamps
    end
  end
end

なお、ALTTER TABLEコマンドを使って、あとから修正する方法は
DB内のデータを書き換えるだけで、アプリ自体のファイル等は編集されない。

mysql
# ALTER TABLE テーブル名 MODIFY COLUMN カラム名 新しい定義
ALTER TABLE ClubStudent MODIFY COLUMN student references

つまり、原因の根本的な部分を修正できないので、駄目

render partial: 部分テンプレの参照

render レンダリング(render) - railsドキュメント

全てのページのヘッダー(上部)に、ログアウトや他のstudentやclubs等のリンクを乗せる
tempsnip.jpg

共通して表示させるので、/app/views/layouts/application.html.erb を編集する。
なお、部分テンプレファイル名は『_』アンダーバー始まり

/app/views/layouts/application.html.erb
<body>
# <%= render :partial => '部分テンプレ名' %>
  <%= render :partial => 'shared/header' %>
</body>

表示させたいリンクを書きこむ。

/app/views/shared/_header.html.erb
<%= link_to 'Student list', students_path %> 
<%= link_to 'subjects list', subjects_path %> 
<%= link_to 'clubs list', clubs_path %> 
<%= link_to 'exam_result list', exam_results_path %>
<%= link_to 'club_stdent list', club_students_path %>
<%= link_to 'Log Out', destroy_student_session_path, method: :delete %>

validation

参考リンク Active Record Validations
バリデーションは有効なデータだけをDBに保存するのを確実にするための最善策。

今回の実装先:clubの新規作成ページ
newclub_form.JPG

validate条件

空でないこと

validates :name, presence: true
# 因みに、空が条件ならば
# validates :name, absence: true

入力文字の長さ

文字の最大長は、データ型を要参照。varcharなら255文字まで

/app/models/club.rb
# 2文字以上
validates :name, length:{minimum:2}
# 255文字以上
validates :name, length:{maximum:255}

exclusion含まない

授業ではしなかったが、クラブ名末尾に『部』を入れない、という条件を追加してみる

/app/models/club.rb
validates :name, exclusion: { in: %w(部 サークル) }
# 『含む』ならinclusion

実装結果

空白や文字列長、『サークル』という語には、validatesが発動した
validate-clubnew.JPG

ただ、現状だと、『テニスサークル』の様に文字列と連結すると、validateが動かない
あとまわし

Club was successfully updated.

Name: サークル部

type "unsigned" does not exist (※Postgresql)

validatesの実装していく最中に、エラーに気づいた

studentのeditページで更新すると、

ActiveRecord::StatementInvalid in StudentsController#show

PG::UndefinedObject: ERROR: type "unsigned" does not exist LINE 1: ...id as subject_id, CAST(AVG(exam_results.score) as unsigned) ... ^ : SELECT subjects.id as subject_id, CAST(AVG(exam_results.score) as unsigned) as avg_score, MAX(exam_results.score) as max_score, MIN(exam_results.score) as min_score FROM "students" INNER JOIN "exam_results" ON "exam_results"."student_id" = "students"."id" INNER JOIN "subjects" ON "subjects"."id" = "exam_results"."subject_id" GROUP BY subjects.id ORDER BY subjects.id

と、エラーを吐き、因みに、ブラウザの戻るボタンで戻ると、更新されている。
また、エラー原因であると思わる、StudentController#showは

app/controllers/students_controller.rb#show
def show
    @students = Student.joins(:subjects)
                       .select('students.name, students.email, students.age, students.gender, students.opinion, subjects.id as subject_id')
                       .select('exam_results.name as exam_result_name, subjects.name as subject_name, exam_results.score')
                       .select('CAST((exam_results.score / subjects.max_score) * 100 as unsigned) as ratio')
                       .where(id: params[:id])

    avg_result = Student.joins(:subjects)
                        .select('subjects.id as subject_id')
                        .select('CAST(AVG(exam_results.score) as unsigned) as avg_score')
                        .select('MAX(exam_results.score) as max_score')
                        .select('MIN(exam_results.score) as min_score')
                        .group('subjects.id')
                        .order('subjects.id')
(以下略)

因みに、このcontrollerは、以前の大学データのcontrollerからコピーしてきたものだ。
つまり、MySQLで動くアプリのcontroller。

unsigned (MySQL)

  • MySQLにおいては正と負の整数を扱うことができる。
  • unsignedを指定すると、正の数しか格納できなくなり、代わりに範囲が2倍になる。
  • unsignedにした値が負になると、エラーを起こす
    • UNSIGNEDは、マイナス値が入らないだけでなく、マイナスになる計算もできない。
    • CASTで一時的に型を変える事で回避は可能。

Postgresqlにはunsined型は存在しない(最重要)

対応策

まだ、試験結果のデータを入れてないので、功を奏すか分からないけれども

  • unsignedをint等の型に置き換える
    • 今回は試験点数を扱っていて、intで事足りると思われる。
    • ただ、MySQLでint unsignedだと、範囲が正の方向に2倍になっている。
    • 扱う数によっては、intより1つ上ののbigintに変える必要がある
  • CAST as unsignedの部分を消す
    • MySQLでCAST as unsingedは、一時的に型を指定している

前回の大学データに倣って、今回はcast as intに変更した

app/controllers/students_controller.rb
# (該当部分だけ抜き出し)
.select('CAST((exam_results.score / subjects.max_score) * 100 as int) as ratio')

.select('CAST(AVG(exam_results.score) as int) as avg_score') 

正常に、studentデータのedit、updateが機能した。

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

NginxでSSLとロードバランサーの設定

はじめに

Ruby on Railsのフロントウェブサーバとしてnginxを使用するときのSSLとロードバランサー(upstream)の設定について記述します。

ブラウザ --https--> nginx --http--> Ruby on Rails(unicorn,pumaなど)

前提

OSはCentOS7を使用
SSL証明書はあらかじめ用意しておきます。

/etc/pki/tls/certs/server.crt
/etc/pki/tls/private/server.key

nginxのインストールはこちらを参考させていただきました。

CentOS7 に nginx導入

nginxのインストール

上記の参考のとおり、yumでインストールしました。

yum.repos.dの設定

$ sudo vi /etc/yum.repos.d/nginx.repo
...

インストール

$ sudo yum install nginx
...

インストールしたnginxのバージョンは
nginx.x86_64 1:1.15.10-1.el7_4.ngx
です。

nginxの起動と自動起動設定

systemctlで自動起動設定と起動します。

$ sudo systemctl enable nginx
Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service.
$ sudo systemctl start nginx
$

ここから本題

タイトルのとおり、SSLとロードバランサーの設定を行います。

$ sudo vi /etc/nginx/conf.d/ssl_lb.conf
---
upstream backend {
  server localhost:3000; #バックエンドプロセスは同じサーバ内で起動している前提、ポート3000
}

server {
  listen      443 ssl;
  server_name sample.co.jp;

  ssl_certificate     /etc/pki/tls/certs/server.crt;
  ssl_certificate_key /etc/pki/tls/private/server.key;

  root /path/to/root; #コンテンツのルートパスを設定

  location / {
    try_files $uri @backend;
  }

  location @backend {
    proxy_set_header Host              $http_host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://backend;
  }

}

upstream

upstreamに振り分ける対象のサービスを記載します。(ロードバランサー)
複数記述することでロードバランスされます。
また、unix socket接続の記載も可能です。

server

ssl_certificate、ssl_certificate_keyにSSL証明書のファイルを指定します。
upstreamで設定したbackendは"location @backend"に設定します。

nginxの再起動

nginxを再起動することで上記の設定が反映されます。

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

gitを使用したブランチ作成からpushまでの簡単な流れ

ずっとgithub desktopを使用していましたが、さすがにコマンドでも操作できた方がいいだろうと思い、色々コマンドを調べて、ざっくりとした使い方がわかってきたので流れを簡潔に書きます。

gitを使用したブランチ作成からpushまでの簡単な流れ

作業を開始するために、、、

git branch -a #今いるブランチを確認 (-aをつけることでリモートブランチも見れる)
git branch ブランチ名 #ブランチ作成
git checkout ブランチ名 #ブランチ移動  

ファイルを編集したら、、、

git status #ファイルの編集状態確認
git add . #コミットしたいファイル追加("."ではなくディレクトリを指定する事で個別に追加する事ができる)
# 例git add app #appディレクトリのファイル変更分のみコミット対象にする
git commit -m "コミットコメント" #addしたファイルをコミット
git push origin HEAD 
git push origin ブランチ名 
#コミットをリモートにpushする。HEADと記述するとわざわざブランチ名を書かなくてよくなる

pushしたcommitをmasterにマージできたら、、、

git checkout master #masterブランチに移動
git pull #リモートからマージしたファイルを取得する

備忘録として書きました。細かい説明はいらないけど、とりあえず使いたいという人向けにこれからブラッシュアップしていくつもりです。
最初、commitとpushの違いがわからなかったのでそれについても記載したいです。

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

Rails勉強ネタ resources で名前付きヘルパー変わること確認

普段何気なくroutes.rbにてresourcesメソッドを使用しているが、
色々確認する。

usersのコントローラーを作成(定番の7つのメソッド)

$ rails g controller users index new show create edit update destroy
users_controller.rb
class UsersController < ApplicationController
  def index
  end

  def new
  end

  def show
  end

  def create
  end

  def edit
  end

  def update
  end

  def destroy
  end
end
Rails.application.routes.draw do
  get 'users/index'
  get 'users/new'
  get 'users/show'
  get 'users/create'
  get 'users/edit'
  get 'users/update'
  get 'users/destroy'
end

この状態でrails routesを実行すると。

$ rails routes
       Prefix Verb URI Pattern              Controller#Action
  users_index GET  /users/index(.:format)   users#index
    users_new GET  /users/new(.:format)     users#new
   users_show GET  /users/show(.:format)    users#show
 users_create GET  /users/create(.:format)  users#create
   users_edit GET  /users/edit(.:format)    users#edit
 users_update GET  /users/update(.:format)  users#update
users_destroy GET  /users/destroy(.:format) users#destroy

全部users_'アクション名'という名前付きヘルパーになって、メソッドは全部GETです。
(空気を読んでPOSTとかDELETEなどになるのかと思っていた・・・)

resoureceメソッド使う

routes.rb
Rails.application.routes.draw do
  # get 'users/index'
  # get 'users/new'
  # get 'users/show'
  # get 'users/create'
  # get 'users/edit'
  # get 'users/update'
  # get 'users/destroy'

  resources :users
end

この状態でrails routes実行すると

$ rails routes
WARNING: Nokogiri was built against LibXML version 2.9.7, but has dynamically loaded 2.9.4
   Prefix Verb   URI Pattern               Controller#Action
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

Restfulな形になった。
ニュアンス的にcreateuser_path のPOSTなイメージがあるが、
users_pathである事に注意(間違えそう)。

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

Rails勉強ネタ resources での名前付きヘルパー確認

普段何気なくroutes.rbにてresourcesメソッドを使用しているが、
色々確認する。

usersのコントローラーを作成(定番の7つのメソッド)

$ rails g controller users index new show create edit update destroy
users_controller.rb
class UsersController < ApplicationController
  def index
  end

  def new
  end

  def show
  end

  def create
  end

  def edit
  end

  def update
  end

  def destroy
  end
end
routes.rb
Rails.application.routes.draw do
  get 'users/index'
  get 'users/new'
  get 'users/show'
  get 'users/create'
  get 'users/edit'
  get 'users/update'
  get 'users/destroy'
end

この状態でrails routesを実行すると。

$ rails routes
       Prefix Verb URI Pattern              Controller#Action
  users_index GET  /users/index(.:format)   users#index
    users_new GET  /users/new(.:format)     users#new
   users_show GET  /users/show(.:format)    users#show
 users_create GET  /users/create(.:format)  users#create
   users_edit GET  /users/edit(.:format)    users#edit
 users_update GET  /users/update(.:format)  users#update
users_destroy GET  /users/destroy(.:format) users#destroy

全部users_'アクション名'という名前付きヘルパーになって、メソッドは全部GETです。
(空気を読んでPOSTとかDELETEなどになるのかと思っていた・・・)

resoureceメソッド使う

routes.rb
Rails.application.routes.draw do
  # get 'users/index'
  # get 'users/new'
  # get 'users/show'
  # get 'users/create'
  # get 'users/edit'
  # get 'users/update'
  # get 'users/destroy'

  resources :users
end

この状態でrails routes実行すると

$ rails routes
   Prefix Verb   URI Pattern               Controller#Action
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

Restfulな形になった。
ニュアンス的にcreateuser_path のPOSTなイメージがあるが、
users_pathである事に注意(間違えそう)。

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

技術選定まとめ

技術選定まとめ

おはこんにちばんわ
マンハッタンコードエンジニアのがーたろーです。

今回プロジェクトで技術選定を行いました。
どのような観点、要素で技術を選定するべきなのかを学んだのでまとめたいと思います。

技術選定に必要なもの

観点:エンジニア、開発チームが作業するのに最適だと思えるものを選定すること

上記の最適だと思えるものには

  1. 目的
  2. メリット
  3. できることの比較

これらを満たしていることが前提になります。

今回選定の候補に選ばれた言語は
1. Kotlin/SpringBoot
2. ruby/Ruby on Rails
3. php/Laravel

の三つから選びます。

目的

API通信、管理画面、複数のサーバーとデータ通信をするためのサーバーを作る
オニオンアーキテクチャ、DDDの理解、学習
GraphQL使いたい
テスト駆動

メリット&デメリット

kotlin/SpringBoot

メリット
今回のプロジェクトではアプリ側(スマートフォン)の開発も行うため、Andoroidの開発にKotlinを用いる。
そのため学習する言語がSwift+Kotlinの2言語で済む。
DDD(ドメイン駆動開発)と親和性が高い

デメリット
インフラにかかる影響の判断が他の2言語と比べて手間がかかる。(tomcatの設定周り)
サーバーでKotlin(Java)を使う場合、Tomcatが必須になる。
Tomcatを利用する場合、Nginxとは異なりユーザーの最大同時接続数の管理がスレッドをいくつまで立てられるかの設定になる。
何スレッドまで利用するのかはサーバースペックを元に試算しなければ行けないためサーバーのスペック調査
設定数のMaxギリギリまで接続した場合の挙動のテストをして性能を担保しないといけない(やること増える)

Ruby/Ruby on Rails

メリット
プロジェクトにRuby on Railsを触ったことのあるエンジニアが3人いる。
ディレクトリ構造など規約があるため、設計の話し合いコストが減る。
ライブラリがとにかく便利(使ったことあるやつがそのまま使えそう)

デメリット
DDDとrailsの作りがおそらくケンカする。
DDDでやりたいことがrailsに合わせないと、そもそも作れなくなるためサーバー側だけ全体と作りが変わってきてしまう。

PHP/Laravel

メリット
自由
DDDと親和性がある
railsを参考に作っているためライブラリの使い方が似ていそう。
ローカルでの環境作成が容易なため、開発に入るまでのスピードが早い。

デメリット
規約、設計を設けないと開発現場が魔界と化す

できることの比較

候補 サーバ設定の難易度 GraphQL DDD 規約 テスト
Kotlin/SpringBoot 性能試験が必要 チューニング OK ktlint Spek Mockit
Ruby/Ruby on Rails nginx(むずかしくない) OK Rubocop Rspec
PHP/Laravel nginx(むずかしくない) OK PSR phpUnit phpspefc

ミドルウェアはnginxを全部使う想定。
Kotlinだけ考えること増える。
規約、テストは使うものが違うだけで問題はない。

ぶっちゃけどれ使いたい?

ここまで議論した結果、要件はどれでも実現可能
学習コスト的にはKotlinサーバーの書き方はAndroidとは異なる
やったことある人が多いのはメリットだが少ないのはデメリットではない(世界で使っている人が少ないのは別)
DDD・オニオンアーキテクチャに沿った開発をしたい。やらないともったいない。
↑これに力を入れたいのでデメリット=インフラ課題を増やしたくはない

上記のことを考慮した結果

選ばれたのはPHP/Laravelでした

まとめ

今回私は開発チームの一員として使用する言語の技術選定を行いました。
その中で、何を観点に持たないといけないのか、懸念点、優先すべき事柄、そういった判断基準を身に着けるいい機会を頂けました。
この話をするときにもっといろんな知識があったらもっと別の視点でも話ができたんだろうなぁ、と次にする際にはいろんな知識を身につけてから挑戦したいなぁ、と思いました。

インフラって全くワカンねぇなぁ。

ファンを増やしたいのでもしこの記事いいなぁ、お前やるじゃんとか思ったらファンメッセージください!
もし会社のHPに載っけてもいいぜって人いたらどうぞよろしくお願いします!

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

[備忘録] RDSで外部サーバからアクセス許可する方法

経緯

開発環境でVagrantを使用していて、EC2を経由せず、ローカル環境からRDSを接続し
データベースの操作を行いたいと思ったから。
今回はMySQLを例に書いていきます。

RDSとは

「Amazon Relational Database Service (通称:RDS)」はAmazonの提供する、リレーショナルデータベース構築サービスことです。
より詳しい情報は下にリンクを貼っておきます。
https://dev.classmethod.jp/cloud/aws/cm-advent-calendar-2015-aws-re-entering-rds/#overview

RDSの設定

設定にパブリックアクセシビリティという項目があるので「はい」に変更する。
新規設定のときは「[詳細設定]の設定」、既存DBの設定変更のときは設定変更を選択後の一覧画面に表示される。
下の画像は新規設定の時の画面です。
スクリーンショット 2019-03-26 23.45.38.png

セキュリティグループの設定

MySQLに紐づくセキュリティグループの設定を行う。
VPCセキュリティグループのリンクをクリックする。
スクリーンショット 2019-03-27 0.15.06.png

セキュリティグループに接続許可するサーバーや接続したい場所のIPアドレスを追加する。
※今回は、「0.0.0.0」にしてますが、これは全てのIPアドレスからの接続許可することになるので、現在使用しているIPのみ許可する方がセキュリティ的にいいと思います。
スクリーンショット 2019-03-27 0.50.49.png

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

CentOS7にRubyをインストール

いつも忘れがちなので、自分用のメモも兼ねてインストール方法を投稿します。

前準備

CentOS7はMinimal ISOでインストール済みであること

必要なパッケージのインストール
$ sudo yum -y install git bzip2 gcc gcc-c++ openssl-devel readline-devel zlib-devel
$ sudo yum -y install epel-release
$ sudo yum -y install nodejs
rbenvをダウンロード
$ sudo git clone https://github.com/rbenv/rbenv.git /usr/local/rbenv
$ sudo git clone https://github.com/rbenv/ruby-build.git /usr/local/rbenv/plugins/ruby-build
rbenvの環境設定

/etc/profile.d/rbenv.shを作成し、以下の内容を追加します。

export RBENV_ROOT=/usr/local/rbenv
export PATH=${RBENV_ROOT}/bin:$PATH
eval "$(rbenv init --no-rehash -)"

rootユーザで以下の実行が必要

# source /etc/profile.d/rbenv.sh

sudoでrbenvを実行するためにsudorderにRBENV_ROOTとsecure_pathの追記が必要

# visudo
...
Defaults env_keep += "RBENV_ROOT"
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/rbenv/bin:/usr/local/rbenv/shims
...

Rubyのインストール

バージョンをチェック
$ rbenv install -l
...
  2.6.0-rc1
  2.6.0-rc2
  2.6.0
  2.6.1
  2.6.2
  2.7.0-dev
  jruby-1.5.6
...
インストール実行

バージョン2.6.2をインストールしてみます。

$ sudo rbenv install 2.6.2

使用するRubyのバージョンを設定します。

$ sudo rbenv global 2.6.2
$ sudo rbenv rehash

これでインストール完了です。

後処理

必要なgemをインストール

bundlerはRailsを動かすために最低限必要なのでインストールします。

$ sudo gem install bundler --no-doc
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む