LoginSignup
8
11

More than 1 year has passed since last update.

[Rails]親子同時保存(cocoon)とリコメンド機能(おすすめ機能)の実装

Last updated at Posted at 2020-02-15

実装すること

今回は私が作成した、「お気に入りのコーヒーを投稿するポートフォリオ」で実装した「あなたへのおすすめ機能」のコードを書いていきます。
簡単にポートフォリオについて説明させて頂くと、
・ユーザーは飲んだコーヒーを投稿します。(Itemテーブル)
・投稿する際に飲んだコーヒーのテイスト(Taistテーブル)を16個のブロックから選びます。
・その投稿に対してユーザーはいいねをすることができます。(Likeテーブル)
・ログインユーザーはマイページに「あなたへのおすすめ」の投稿(Item)が表示されます。
「あなたへのおすすめ」を表示する流れは下記となります。
①ログインユーザーが一番最近いいねした投稿と同じテイストデータの投稿を取ってくる
②①の投稿の中でまだログインユーザーがいいねしていない投稿を取ってくる
③②の投稿の中で最新の投稿を1つ取ってくる(②が無い場合はランダムに1つ取ってくる)

※いいね機能に関しては、下記リンク先で説明しております。
https://qiita.com/yuto_1014/items/78d8b52d33a12ec33448

イメージ

新規投稿をする際に、飲んだコーヒーのテイスト(Taistテーブル)を16個のブロックから選びます。
16個のブロックには、それぞれTaistテーブルの「refresh,bitter,body,fruity」の4つのカラムに入る数字が割り振られています。
(例)左上なら、refresh=3, bitter=0, body=0, fruity=3

コーヒーマップ.png

ER図

Taistテーブルは、投稿(コーヒー)のテイスト(味)データを格納します。
Itemテーブルは、投稿(コーヒー)のデータを格納します。
関係は、Taist:Item = 1:Nになります。

おすすめ機能ER図.png

アソシエーションの確認

Taistモデル

accepts_nested_attributes_forは、Railsが標準で提供している、ActiveRecordのメソッドの一つです。モデル同士が関連付けられている時に、ネストさせることで一度にまとめてレコードの更新ができるようになります。
今回はTaistレコードが作成・更新されるとそれに紐づいたItemレコードも作成・更新されるようにしています。
allow_destroy: trueをattributesメソッドに追加することで削除可能になります。削除するには対象となるレコードに_destroy: 1のようなパラメーターを渡します。

app/models/taist.rb

class Taist < ApplicationRecord
	#アソシエーション
	has_many :items
	#親子同時保存(親:taist/子:item)
	accepts_nested_attributes_for :items, allow_destroy: true
end

Itemモデル

app/models/item.rb
class Item < ApplicationRecord

    belongs_to :taist
	belongs_to :user

    has_many :likes, dependent: :destroy
	has_many :liked_users, through: :likes, source: :user

    #refile
	attachment :image

end

投稿機能の作成(コントローラー・ビュー)

taists_controller.rb

親子同時保存(Taistテーブル・Itemテーブル)にはcocoonを使用しています。
画像投稿はrefileを使用しています。

app/controllers/taists_controller.rb
class TaistsController < ApplicationController

   def new
		#cocoonで親子同時保存
		@taist = Taist.new
		@taist.items.build
	end

	def create
		@taist = Taist.new(taist_params)
	      if @taist.save
	      	render :index
	      else
	      	render :new
	      end
	end

	private
	def taist_params
	    params.require(:taist).permit(:fruity, :refresh, :body, :bitter, items_attributes: [:id, :title, :taist_id, :image, :user_id])
	end
end

taists/new.html.erb

・Taistモデルの子要素であるItemモデルは、fields_forを使用してフォームを作成します。
f.label '✔︎', for: 'refreshA'は、id: 'refreshA'のラジオボタンに紐づいています。
・refresh以外のbiiter,body,fruityはhidden_fieldで隠し、disabled: "disabled"で初期状態では保存されないようにしています。javascriptを使用して、id=refreshAがチェックされていた時は、class=refreshAのhidden_fieldを持つdisabled: "disabled"を解除することで、特定のデータのみ保存するようにします。

app/views/taists/new.html

<h1>新規投稿</h1>
<h5>コーヒーマップ</h5>
<%= form_for(@taist, url: users_taists_path, remote: true) do |f| %>
    <%= f.fields_for :items do |item| %>
        <%= render 'item_fields', f: item %>
    <% end %>
    <div class="refresh_contentA">
	      <%= f.radio_button :refresh, 3, class: "radio_btn", id: 'refreshA' %>
	      <%= f.label '✔︎', for: 'refreshA' %>
	      <%= f.hidden_field :bitter, :value => 0, disabled: "disabled", class: 'hiddenA' %>
	      <%= f.hidden_field :body, :value => 0, disabled: "disabled", class: 'hiddenA' %>
	      <%= f.hidden_field :fruity, :value => 3, disabled: "disabled", class: 'hiddenA' %>
	</div>									 
        ... <!-- refresh_contentがA-Qの16個あります。 -->
        <%= f.submit "保存", class:"form-control" %>
<% end %>

taists/_item_fields.html.erb

hidden_fieldでuser_idにcurrent_user.idを代入しています。

app/views/taists/_item_fields.html

<div class="nested-fields form-inline">
    <p>画像投稿</p>
		<%= f.attachment_field :image %>
    <p>タイトル</p>
		<%= f.text_field :title, class: "form-control" %>
    <%= f.hidden_field :user_id, :value => current_user.id %>
</div>

application.js

チェックされたrefreshIDに紐づいた'disabled'を解除します。

app/assets/javascripts/application.js
$(document).on("turbolinks:load", function() {

  		$('#refreshA').click(function() {
      			$(".hiddenA").attr('disabled', false);
  		});

        // #refreshA-Qまで記載しています。
});

おすすめを表示する(コントローラ・ビュー)

大変お待たせしました。おすすめ機能を表示するロジックを記載致します。

users_controller.rb

app/controllers/users_controller.rb

class UsersController < ApplicationController

  def show
		@user = User.find(params[:id])
        #@userがいいねした投稿
		@likes = Like.where(user_id: @user.id).order(created_at: :desc)
        if @likes.exists?
            #like = @userがいいねしている投稿の最新1つ
            like = @likes.last
            #item = likeのitem_idが含まれるitem
            item = like.item
            #item_recommend = itemと同じtaist
			taist_recommend = Taist.find_by(refresh: item.taist.refresh, bitter: item.taist.bitter, body: item.taist.body, fruity: item.taist.fruity)
            #item_recommend = taist_recommendと同じitemを全て
			item_recommend = taist_recommend.items
            #いいねしたitem全てのitem.id
			x = [] #xに配列を代入できるようにする
			@likes.each do |like|
				x << like.item_id #xにlike.item_idを全て代入して配列にする
			end
            #like_recommend = item_recommendの中で@userがいいねしていないitemの最新1つ
			@like_recommend = item_recommend.where.not(id: x).last
        else
			@like_recommend = Item.order("RAND()").last
        end         
  end


end

users/show.html.erb

app/views/users/show.html

<h1>あなたへのおすすめ</h1>
<h3>タイトル</h3>
    <%= @like_recommend.title %>
<h3>投稿</h3>
    <%= attachment_image_tag @like_recommend, :image %>

最後に

最後までご覧いただきありがとうございます。
初学者ですので間違っていたり、分かりづらい部分もあるかと思います。
何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。

8
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
11