Spreeに関連商品表示機能を追加する

Spreeにリコメンド（関連商品）を表示する機能を追加しましょう。

このエクステンションを使用します。

[spree_related_products](https://github.com/spree-contrib/spree_related_products)

## エクステンションのインストール

Githubに書いてあるとおりです。まずはGemfileに以下を記載。`branch`の部分はSpreeのバージョンにあわせて書き換えてください。

```rb
gem 'spree_related_products', github: 'spree-contrib/spree_related_products', branch: '3-0-stable'
```

次にこれらのコマンドを実行。

    bundle install
    bundle exec rails g spree_related_products:install
    bundle exec rake db:migrate

以上です。`rails s`でサーバーを起動してみましょう。

管理者画面の商品ページに新しく「Related product」タブが登場し、こんな感じで関連商品の設定ができるようになります。

![relation.png](https://qiita-image-store.s3.amazonaws.com/0/7323/fb779b35-81fa-4f71-09c1-740864df8e1e.png)


ただ他のエクステンション等と異なり、このエクステンションにはデフォルトのビューが作られていません。

管理画面は上の写真のようにインストール後すぐに使えるようになるのですが、エンドユーザー向けのページはコードを書く必要があるので注意してください。

## RelationモデルとRelationTypeモデルについて

このエクステンションを使うと、関連する商品だけでなく、その関係性まで一緒に定義できるなど高機能で融通の効く設計となっています。その反面、概念が少々わかりにくいのでちょっとだけ解説しておきます。

このエクステンションで新しく作るモデルは２つ。`Spree::RelationType`と`Spree::Relation`です。

商品同士の関連を定義するのは`Spree::Relation`で、親商品の外部キー`:relatable_id`と子商品の外部キー`:related_to_id`があります。関係性自体は多対多の自己参照モデルですね。

さらに`Spree::Relation`は、外部キー`relation_type_id`を持ち、`Spree::RelationType`に一対多で`belongs_to`します。そして`Spree::RelationType`は、アクセサリー、類似商品、セット商品など商品同士の関連性を定義しています。

つまり、ひとつの商品がアクセサリーとしての関連商品、類似商品としての関連商品などなど、タイプの異なる複数の関連商品を持つ構造になります。



## ビューの作成

とりあえずさくっと表示だけしてみましょう。

partial挿入用のDefaceを作成。

```ruby:app/overrides/add_relations_to_product.rb
Deface::Override.new(
  virtual_path: 'spree/products/show',
  name: 'add_relations',
  insert_after: '[data-hook="product_properties"]',
  partial: 'spree/shared/relations'
)
```

partialファイルを作成。

```erb:app/views/spree/_shared/_relations.html.erb
<h3 class="product-section-title"><%= Spree.t(:related_products) %></h3>
<ul class="list-group">
  <% @product.relations.each do |r| %>
    <li class="list-group-item"><%= link_to r.related_to.name, r.related_to %></li>
  <% end -%>
</ul>
```

表示されました！

![relate2.png](https://qiita-image-store.s3.amazonaws.com/0/7323/d05a113c-820e-d9f9-287f-4cf7f2bc29aa.png)

なお、`@product.relations`のメソッド名は`RelationType`のタイプ名をそのまま使用できるみたいです。例えば'Accessories'といったタイプ名にしておくと、`@product.accessories`といったメソッドで表示できちゃうようですね。


## 日本語化

どのみちビューは自分で作らなければならないので無くても困ることはなさそうですが、一応日本語用のロケールファイルを作りましたので公開しておきます。

そのまま`config/locales`に設置するだけで管理画面が翻訳されるかと思います。

[spree_related_products_ja.yml](https://gist.github.com/kamiya54/0184467ba80996eac631)

## ついでに

なお節々のカラムからわかるように、このエクステンションのRelationはポリモーフィックで`Spree::Product`以外のモデルも扱えるようになってるみたいです。私も詳しく検証してないのですが、よくわからない場合は気にせず使ってしまって大丈夫そうですね。

## ハマった点

原因はわからないのですが、特定の条件下で関連商品設定画面を開こうとすると`NoMethodError in Spree::Admin::Products#related`が発生することがあるようです。

```
undefined method `empty?' for nil:NilClass
Extracted source (around line #6):       
4 <%= csrf_meta_tag %>
5 
6 <% if @relation_types.empty? %>
7   <div class="alert alert-warning no-objects-found">
8     <%= Spree.t(:no_relation_types) %>
9   </div>
```

以下のGithub issueを参考に修正しました。

[undefined method `empty?' for nil:NilClass #118](https://github.com/spree-contrib/spree_related_products/issues/118)

Spreeアプリケーション内に下記のファイルを設置すると動きます。

```rb:app/controllers/spree/admin/products_controller_decorator.rb
Spree::Admin::ProductsController.class_eval do
  def related
    load_resource
    @relation_types = Spree::Product.relation_types
  end
end
```