Help us understand the problem. What is going on with this article?

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

実装すること

今回は私が作成した、「お気に入りのコーヒーを投稿するポートフォリオ」で実装した「あなたへのおすすめ機能」のコードを書いていきます。
簡単にポートフォリオについて説明させて頂くと、
・ユーザーは飲んだコーヒーを投稿します。(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 %>

最後に

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

Twitter:https://twitter.com/yto_oct
note:https://note.com/yto_oty

yuto_1014
ご覧いただきありがとうござます。 飲料メーカー営業マン→スクールにてRubyを中心に勉強→Javaエンジニア [twitter]:https://twitter.com/yto_oct [note]:https://note.com/yto_oty
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした