技術系ブログ

とにかく小ネタで

【rails】フォロー機能。追記あり

整理していなかったのでまとめておきます。
参考:第14章 ユーザーをフォローする - Railsチュートリアル

※devise導入済みですすめます。

この機能を導入したら

①view側にフォロー中とフォロワーの数の表示
②フォローボタンの作成
③実際にフォロー中の人、フォロワーを表示
この3つをview側に追加することを目指して作っていきます。

導入前に

twitterのようなフォロー機能は非対称性があります。つまり自分がフォローしていたとしても、
フォローが返されない、とう言う状況もあります。
このことを頭に入れなければいけません。

モデルの導入

rails generate model Relationship follower_id:integer followed_id:integer

そして、 できたファイルに

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

これでfollower_idとfollowed_idの組み合わせが必ずユニークであることを保証します。
マイグレーションファイルに書いてある add_index - Qiita
データベースにindexを張る方法 - Qiita
DBのインデックスと複合インデックス - Qiita

次に$ rails db:migrate

関連付け

userとreleationshipは1対多の関係にあります。

class User < ApplicationRecord
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
end

いつもの関連付けなら、has_many :relationshipsとすることろを、自分がフォローした(能動関係)を作るためにhas_many :active_relationshipsにして、
次に、has_many :active_relationshipsというモデルは無いので
class_name: "Relationship"と書いて、Railsに探して欲しいモデルのクラス名を明示的に伝える。
更に、外部キーを先程作った foreign_key: "follower_id",で指定します。
(ユーザーを削除したら、ユーザーのリレーションシップも同時に削除される必要があります。
そのため、関連付けにdependent: :destroyも追加しています。)    

class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
end

本来ならbelongs_to :user、外部キーがuser_idとしますが、今回は仮想的にフォローする人される人に分けたいのでfollower_idfollowed_idとして関連付けます。
belongs_to :follower, class_name: "User"とすることで
userと関連付けるのをfollower_idと指定します。
また、followerfollowerdというクラスは存在しないのでclass_name: "User" として明記します。
このように書くと、今回はuser_idfollwer_idfollowed_idとして認識されます。
つまり、relationshipモデルのfollower_idやfollowed_idとuserモデルのidがひも付きます。
この結果、下記のメソッドが使えるようになります。

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オブジェクトを返す

バリテーションの追加

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

現時点では、フォローするuserとの1対多の関係ができました。この時点で②フォローボタンの作成 のみならほとんどてきてしまいます。

多対多

一人のユーザーはいくつもフォローするしされる、つまり多対多の関係にあります。

class User < ApplicationRecord
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
  has_many :following, through: :active_relationships, source: :followed
  #追加したのを和訳すると  userはactive_relationshipsを通ってfollowingをたくさん持っている。
 さらに細かく言うと、外部キーがfollower_idと紐付いているuserはactive_relationshipsを通ってfollowed_idをたくさん持っている
end

Active Record の関連付け - Railsガイド
:sourceパラメーター を使って、「following配列の元はfollowed idの集合である」ということを明示的にRailsに伝えます。

この結果、user.followingでユーザーがフォロー中の人がわかります。 つまりfollowed_idを抜き出すことができます。
さらに、user.following.count でフォロー中の人数がわかるようにます。

follow、unfollow、following?メソッドを作成

followingで取得した集合をより簡単に取り扱うために、followやunfollowといった便利メソッドを追加します。

class User < ApplicationRecord

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

# ユーザーをフォロー解除する
  # followed_idを削除
  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
end

フォロワー側

今まではすべて自分からフォローした側でしたが、自分をもフォローされます。
しかし、これはいままでとすべて反対のことをすればいいだけです。

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

この結果、user.followersでユーザーをフォロワーがわかります。 つまりfollower_idを抜き出すことができます。
さらに、user.followers.count でフォロワー数がわかるようにます。

view側

①view側にフォロー中とフォロワーの数の表示
②フォローボタンの作成
③実際にフォロー中の人、フォローされた人を表示
をいよいよ作っていきます。

①view側にフォロー中とフォロワーの数の表示

touch app/views/devise/shared/_stats.html.slimこの中にフォロー中とフォロワーの数の表示を書きます。

#app/views/devise/shared/_stats.html.slim
- @user ||= current_user
.stats
  | フォロー中のユーザー
  = link_to following_user_path(@user)
      div#following = @user.following.count

  | フォロワーリスト
  = link_to followers_user_path(@user)

      div#followers =@user.followers.count

そして、 表示させます。

#app/views/users/show.html.slim(わたしの場合)
= render 'devise/shared/stats'

これで、①view側にフォロー中とフォロワーの数の表示 ができました。 また、

= link_to following_user_path(@user)
= link_to followers_user_path(@user)

次にこのリンクから③実際にフォロー中の人、フォローされた人を表示のviewを作っていきます。

③実際にフォロー中の人、フォローされた人を表示

まずはルーティングから

config/routes.rb
Rails.application.routes.draw do

  resources :users do
    member do
      get :following, :followers
    end
  end

end

このURLで下記のようになります。

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

次に、コントローラー

class UsersController < ApplicationController
  def following
    @user = User.find(params[:id])
    @users = @user.following
    render 'show_follow'
  end
  def followers
    @user = User.find(params[:id])
    @users = @user.followers
    render 'show_follow'
  end
end

render 'show_followを2つのアクションで使い回しても@users の中身は違うので問題有りません。
show_followのviewを作っていきます。 touch app/views/users/ahow_follow.htnl.slim

#follow_show
  .container
    - @users.each do |user|
      .user_content
        = link_to user_path(user)
          = user.username #←.usernameはuserモデルの中のカラム名に合わせてください
②フォローボタンの作成

これで最後です。
まずはコントローラーを作っていきます。  rails generate controller Relationships
次にルーティングです。 フォロー関係を作るか、消すかだけなので

Rails.application.routes.draw do
  resources :users do
    member do
      get :following, :followers
    end
  end

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

次に、コントローラーを書きます

#app/comtroller/relationships_controller.rb
class RelationshipsController < ApplicationController
  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

さらにボタンを作っていきます。 フォローするボタン
touch app/views/relationships/_follow.html.slim

=button_to current_user.active_relationships.build, params: { followed_id: @user.id }
  | フォローする

フォローを解除するボタン touch app/views/relationships/_unfollow.html.slim

=button_to current_user.active_relationships.find_by(followed_id: @user.id), method: :delete
  | 解除する

そして上記のボタンをまとめます。
touch app/views/users/_follow_form.htnl.slim

- unless @user == current_user
  #follow_form
    - if current_user.following?(@user)
      = render 'relationships/unfollow'
    - else
      = render 'relationships/follow'

最後にこれを表示させます。

#app/views/users/show.html.slim(わたしの場合)
= render 'devise/shared/stats'
= render 'users/follow_form'

おしまい。

追記

【rails】フォロー機能。 非同期編 - 技術系ブログ

【rails】任意のviewのみで特定のjsを読み込む。

とっても簡単です。 参考: Rails 必要なJavaScriptのみを読み込む | | KeruuWeb
RailsがJavaScriptやスタイルシートを読み込む仕組み - りょうたくの技術ブログ

Asset Pipelineとは

そもそも、任意のveiwのみでjsが読み込んでくれないとは、Asset Pipelineがしっかり機能しているからです。

アセットパイプライン - Rails ガイド

アセットパイプラインがアセットを連結するとこが第一の機能なのです。 すべてのJavaScriptファイルを1つのマスター.jsファイルに連結し、すべてのCSSファイルを1つのマスター.cssファイルに連結します。
そのため任意のviewのみで読み込むためには手を加える必要があります。

ツリー読み込みの停止

すべて自動で読み込むのを書き換えます。

#app/assets/javascripts/application.js
-    //= require_tree .
+  // require_tree .

読み込ませたいjsファイルを記述

#config/initializers/assets.rb

Rails.application.config.assets.precompile += %w( test.js )

最後に表示させたいviewに読み込ませます

= javascript_include_tag "test.js"

おしまい

HEADS UP! i18n 1.1 changed fallbacks to exclude default locale.

参考:config.i18n.fallbacksのメッセージについて - Qiita

HEADS UP! i18n 1.1 changed fallbacks to exclude default locale. But that may break your application. - Qiita

HEADS UP! i18n 1.1 changed fallbacks to exclude default locale.
But that may break your application.

Please check your Rails app for 'config.i18n.fallbacks = true'.
If you're using I18n (>= 1.1.0) and Rails (< 5.2.2), this should be
'config.i18n.fallbacks = [I18n.default_locale]'.
If not, fallbacks will be broken in your app by I18n 1.1.x.

For more info see:
https://github.com/svenfuchs/i18n/releases/tag/v1.1.0

上記のようなエラーが出てきました。
ちなみにconfig/environments/produciton.rb74行目'config.i18n.fallbacks = true'.と私の場合書いてあります。

gemfile.lockを見ると、

i18n (1.7.0)
rails (5.2.4)

です。
If you're using I18n (>= 1.1.0) and Rails (< 5.2.2),にはあたりません。 i18nのバージョンを下げたりもしましたが、注意が出ます。

これは何もしない感じで

Release v1.1.0 · ruby-i18n/i18n · GitHub

bundle install と bundle updateの違いについて - Qiita

【rails】dockerでのDB(postgersql)操作

dockerでの開発環境を構築し、DBにpostgresqlを使いました。今回はDBにアクセスしてテーブル内容の確認をメモしておきます。

まずは立ち上げる

docker-compose up -d
終了は
docker-compose down
参考: docker-compose コマンド — Docker-docs-ja 1.10.0b ドキュメント

DBに接続

$ docker-compose exec db psql -d database -U username -h host
-d: データベース名(未指定だと、ログインユーザー名のデータベースに接続する)
-U: ユーザ名(未指定だと、ログインユーザー名になる)
-h: ホスト名(未指定だと、localhostになる)

上記の内容はconfig/databese.ymlから参考にする
私の場合は、

default: &default
  adapter: postgresql
  encoding: UTF8
  host: db
  username: postgres
  password:
  pool: 5

development:
  <<: *default
  database: myapp_development

なので
$ docker-compose exec db psql -d myapp_development -U postgres -h db です。
そうすると
myapp_development=#と出てくるのでそのまま操作します。
終了はexitまたは\qです。

postgresql操作コマンド

データーベース一覧表示
postgres=# \l
テーブル一覧を表示
postgres=# \z
テーブル定義を確認
postgres=# \d tablename
tablenameには任意のテーブル名を入れる。

以上です

参考: docker-composeコマンド - Qiita
PostgreSQLコマンドチートシート - Qiita

【rails】モデルの関連付け

ポートフォリオを改修中に記憶が曖昧になったのでまとめておきます。

前置き

モデルがuserpostの場合、かつuser:post =  1:多の関係の場合

主キーと外部キーとは

今回の場合、userテーブルに元々あるidが主キーで、 あとに作る、 追加したpost テーブルにuser_idを外部キーとして 主キーを外部キーに保存することで関連付けができます
主キー側のモデル(belongs_toで指定してる側)が親と考えるとスッキリします。

モデルへの書き込み

postは一人のuserから生み出されますpost belongs_to 単数形

class Post < ApplicationRecord
  belongs_to :user
end
#これでpost belongs to userになる

userはたくさんのpostを持っているのでhas_many 複数形

class User < ApplicationRecord
  has_many :posts
end
#これでuser has many postsになる

これだけで、主キーと外部キーを認識してくれます。

add migrate

$ rails g migration add_user_reference_to_post user:references

※ここでuser_id:referencesとしなくても勝手にuser_idとしてテーブル側には作成してくれるので注意です。
f:id:eiji-hb:20191214190533p:plain

そして
$ rails db:migarte
その結果

class AddUserToPost < ActiveRecord::Migration[5.2]
  def change
    add_reference :posts, :user, foreign_key: true
  end
end

postコントローラー側

実際に使う場合、

class PostController < ApplicationController

    def create
    @post = Post.new(post_params.merge(user_id: current_user.id))
    if @post.save
      redirect_to post_path(@post)
    else
      render :new
    end
  end

...........
private

def post_params
  params.require(:post).permit(:name,:description)
end

view側

#postのviewの場合

@post.user.name
#これでpostを投稿したuserのnameが取得てきます。  

参考: rails generate migrationコマンドまとめ - Qiita

【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】🎸 - Qiita

【rails】slim導入と使い方。

slimについてまとめてなかったのでまとめておきます。

slimとは

Slim は 不可解にならない程度に view の構文を本質的な部品まで減らすことを目指したテンプレート言語、
RailsのViewを出力するためのテンプレートエンジンとして、標準で利用されるのはERBですが、 より簡素に書けるテンプレートエンジンとしてslim があります

参考:slim/README.jp.md at master · slim-template/slim · GitHub
RailsのHTMLテンプレートエンジン、Slimの基本的な記法 - Qiita
【爆速で習得】Railsでslimを使う方法から基本文法まで - Qiita

変換に困ったとき
HTML2slim
ERB 2 SLIM

導入

#gemfile

gem 'slim-rails'
#slimのジェネレーターを提供するslim-rails
gem 'html2slim'
#ERB形式をslim形式に変換してくれるhtml2slim

その後
$ bundle

現時点では、ERB形式のファイルが存在してるので、
$ bundle exec erb2slim app/vies/layouts/ --delete
これで準備はOKです。

使い方

テキスト

|,このパイプより深くインデントされたのがテキストブロックになります

body
  p
    |
      これはテキストブロックのテストです。

制御コード

-ERBで<% %>と書くところを-のみで表すことができます。

出力

= ERBで<%= %>と書くところを=のみで表すことができます.

HTMLタグ

<>を消す、閉じタグはいらない。 インデントを気をつける。

条件式

endは書かなくて、<% %>から-で表す.

-if

-else
のみ

classやid

class =. id =#で置き換える。

#html
<dev class="content">
  <p class="title">タイトル</hoge>
</dev>
<dev id="content">
  <p id="title">タイトル</hoge>
</dev>

#slim
dev.content
  p.title タイトル
dev#content
  p#title タイトル

おわり。

【GIt】一人でPull Request練習

現在、独学でポートフォリオを作成中です。一人のみでgitを触っているとPull Requestなんて全くしないので、良くないとも思い練習します。

※事前に、githubのアカウント、ローカルの開発環境が整っている前提で進めます。

Pull Request

Pull Requestをすると、git clone元のディレクトリにmergeのお願いをするみたいなイメージで捉えてます。

一人で練習する流れ

作成したディレクトリをローカル環境へgit cloneしてbranchに切り替えて内容を変更したら、git add、git commitして、
branchのままgit push origin ブランチ名です。
次にgit hub上で、変更内容を説明してPull Requestし、またPull Requestしたのを自分でmergeするだけです。

詳細:GitHub初心者はForkしない方のPull Requestから入門しよう | qnyp blog