府中産農産物直売所検索ページをとおしてかんたんなrailsアプリつくってみよう

# 概要
* 府中市が公開しているオープンデータを検索する事を事例にして、railsのアプリを作成する方法を丁寧に解説します。

# 作れるモノ
https://fuchu-nosanbutsu.herokuapp.com/

# github
https://github.com/junara/fuchu-nosanbutsu

# 環境
* ruby (rbenv) 2.6.1
* rails 6.0.0 rc 1
* mac

# 使っているGem
* gem
    * [activearecord-import](https://github.com/zdennis/activerecord-import)
    * [ransack](https://github.com/activerecord-hackery/ransack)

# 手順
以下の手順で説明します
1. rails new ! (空のアプリを作ろう)
2. migration ! （テーブル作ろう）
3. import ! （データをインポートしよう）
4. view ! （データを表示しよう）
4. search ! （データを検索できるようにしよう）
5. deploy ! （herokuにアップしてみんなで使えるようにしよう）

## rails new ! (空のアプリを作ろう)
```console
> bundle init
Writing new Gemfile to /Users/junara/IdeaProjects/opendata/Gemfile
> ls
Gemfile      Gemfile.lock 
```

railsをインストールするために `gem "rails"` のコメントをアウトを外します。

```Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails", '~> 6.0.0.rc1'
```

```console
bundle install
# 省略
```

今いる場所にrailsを作成します。

```console
pwd
# 今の場所がわかる
rails new .
```

ここでいちど起動を確認しましょう

```console
rails s
```

`http://localhost:3000` にアクセスします。下記の画面が出たら成功です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/a7802644-1e1f-7bc8-e4c4-d79309846015.png)


## migration ! （テーブル作ろう）
今回対象となるデータを見ます。 [府中産農産物直売所マップ（平成30年11月現在） （CSV：11KB）](http://www.city.fuchu.tokyo.jp/kurashi/nogyonituite/tokusan/nousannbutsuchokubaijomap.html)

インポートするデータを府中市のHPからダウンロードして中身を見ます。
[府中産農産物直売所マップ（平成30年11月現在） （CSV：11KB）](http://www.city.fuchu.tokyo.jp/kurashi/nogyonituite/tokusan/nousannbutsuchokubaijomap.html)

エディタによっては、へんな文字が羅列されているかもしれません。
これは、文字コードをうまく認識できていないことが問題です。
文字コードは複数種類ありますが、今だったらshift-jisとutf-8どちらかを試せば解決することが多いです。（エディタによってやり方が違うのでそちらは各自しらべてください。）
今回はshift-jisを選ぶと文字がキチンと日本語で表示されます。

```db/30chokubaijo_map.csv
№,名 前,直売所住所,電話番号,営業時間,販売期間,休業日,販売品目,コメント
# 省略
```

取り込むmodelを作成します。model名とカラム名を考えましょう
今回は、お店のデータなのでテーブル名は Store とします。

```console
 rails g model store                                                                                                     ?[HEAD]
Running via Spring preloader in process 84006
      invoke  active_record
      create    db/migrate/20190310103813_create_stores.rb
      create    app/models/store.rb
      invoke    test_unit
      create      test/models/store_test.rb
      create      test/fixtures/stores.yml

```

次にカラム名です。カラム名は、取り込みたいデータのないようで決めます。
取り込みたいデータとは、前記のCSVをみて考えます。
もちろん、全部のデータを取り込むこともできます。ただ、無駄なデータを取り込むのはデータベースを肥大化させるだけなので、まずは必要なデータ、使用する列だけを取り込みます。
とりあえず、今回必要とするのは `名 前` と `販売品目` 列とします。
これにあわせてmigrationを作成しましょう


```db/migrate/20190310103813_create_stores.rb
class CreateStores < ActiveRecord::Migration[6.0]
  def change
    create_table :stores do |t|
      t.string :name, comment: '名 前'
      t.string :description, comment: '販売品目'
      t.timestamps
    end
  end
end
```

migrationファイルが作られたら、migrationを実行して、tableを作成しましょう。

```console
rails db:migrate                                                                                                        ?[HEAD]
== 20190310103813 CreateStores: migrating =====================================
-- create_table(:stores)
   -> 0.0113s
== 20190310103813 CreateStores: migrated (0.0113s) ============================
```

これで、必要なmodel, tableの作成が終了しました。

## import ! （データをインポートしよう）

下記のgemを入れます。importするときの定番。

```Gemfile
gem 'activerecord-import'
```

```console
bundle install
# 省略
```

取り込むためのメソッドをStoreモデルに書きます。

```
require 'csv'
class Store < ApplicationRecord
  def self.import_csv(file)
    list = []
    CSV.foreach(file, encoding: 'cp932', headers: true) do |row|
      list << self.new(store_params(row))
    end
    import list
  end

  def self.store_params(row)
    {
        name: row['名 前'],
        description: row['販売品目']
    }
  end
end
```

先ほど `db/30chokubaijo_map.csv` に保存したcsvをインポートします。

```irb
rails c
irb > Store.import_csv('db/30chokubaijo_map.csv')
   (0.2ms)  SELECT sqlite_version(*)
   (0.1ms)  begin transaction
   (0.1ms)  SAVEPOINT active_record_1
# 省略
   (0.1ms)  RELEASE SAVEPOINT active_record_1
   (0.8ms)  commit transaction
=> #<struct ActiveRecord::Import::Result failed_instances=[], num_inserts=1, ids=[], results=[]>
```

保存されているか確認します。

```irb
irb > Store.first.description
  Store Load (0.2ms)  SELECT "stores".* FROM "stores" ORDER BY "stores"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> "トマト、きゅうり、おくら、いんげん、キャベツ、さつまいも、ブロッコリー、ねぎ、なす、モロヘイヤ、ピーマン、紫イモ、カリフラワー、じゃがいも、里いも、大根、ほうれん草、水菜　ほか"
irb(main):009:0>  Store.count
   (0.2ms)  SELECT COUNT(*) FROM "stores"
=> 46
```

## view ! （データを表示しよう）

ルートを追加します。

```config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'
end
```

上記で追加したhomeのアクションを行うコントローラーを新規で追加します。


```app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    @stores = Store.all
  end
end
```

```app/views/home/index.html.erb
<h2>
  府中産農産物直売所一覧
</h2>
<% @stores.each do |store| %>
  <div>
    <h3>
      <%= store.name %>
    </h3>
    <div>
      <%= store.description %>
    </div>
  </div>
<% end %>
```

ここで確認します。
http://localhost:3000/

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/96a7f855-5a4f-aa76-584b-a902287f7299.png)

## search ! （データを検索できるようにしよう）

Activerecordでやるのは面倒なので、定番の検索のgem ransackを入れます。

```Gemfile
gem 'ransack'
```

で、インストール

```console
bundle install
```

検索するために、indexアクションを修正します。
ransackの使い方は[公式](https://github.com/activerecord-hackery/ransack#search-matchers)をどうぞ。

```app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    @stores = Store.ransack(description_cont: params[:keyword]).result
  end
end
```

検索フォームをviewについかします。

```
<h2>
  府中産農産物直売所一覧
</h2>

<div>
  <%= form_with url: root_path, method: :get, local: true do |f| %>
    <%= f.text_field :keyword, value: params[:keyword], autocomplete: 'off' %>
    <%= f.submit '検索' %>
  <% end %>
</div>
<% @stores.each do |store| %>
  <div>
    <h3>
      <%= store.name %>
    </h3>
    <div>
      <%= store.description %>
    </div>
  </div>
<% end %>
```

ためします。
入力ボックスに `乳酸菌飲料` と入力すると下記のようになれば成功です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/fda5e0cf-b86b-4ed8-950f-b8300977f05f.png)

## deploy ! （herokuにアップして世界のみんなに使ってもらおう）

developmentとtest環境ではsqlite3, productionではpgをインストールします。

```Gemfile
group :development, :test do
  gem 'sqlite3'
end

group :production do
  gem 'pg'
end
```

herokuへpushと同時に migrationが走るようにしましょう

```Procfile
release: bundle exec rake db:migrate
```

herokuのアプリを作ります。

```console
heroku create fuchu-nosanbutsu
```

現行のmasterをherokuへpushします。

```
git push heroku master
```

うまくdeployされたかみましょう

```
heroku open
```

こういう表示なら成功です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/3d9bb315-f810-32ae-d301-a9cfb395e12d.png)

次にデータをインポートします。herokuでrails cをします。

```console
heroku run rails c
```

既にやったようにインポートをします。

```console
Store.import_csv('db/30chokubaijo_map.csv')
Store.count # -> 46
```

終わったらexit

```
exit
```

herokuのページを再度リロードすると前述と同様に、一覧が表示され、検索も可能なことが確認できます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/96a7f855-5a4f-aa76-584b-a902287f7299.png)

完成です！！！これで、世界の人に府中市の農産物をアピールできました！


# さらに発展させるには？

作ったのは、とてーもシンプルなアプリです。更に発展させるともっと使いやすいアプリになります。ご自身の興味にしたがって色々やってみましょう。
下記は、拡張例です！
是非挑戦しましょう！

### かんたんめ
* インポート対象を追加する：元のCSVには、住所などの列もあります。そちらもインポート対象にしましょう。
* インポートした列を表示に追加しましょう。
* 検索結果にページネーションを加えましょう
* 検索キーワード結果の文章にマッチしたキーワードをハイライトさせましょう。
* 複数のカラムを対象に検索できるようにしましょう
* bootstrapを導入して、装飾しましょう
* bulmaを導入して装飾しましょう
* 住所を押した時、google mapに遷移させましょう
* データを追加、編集できるフォームを追加しましょう
* herokuにdeployしましょう

### そこそこ
* インポート時、重複列がある場合はupdateにしましょう
* 空白スペースで区切った場合はand検索になるようにしましょう
* 検索をページ遷移せずajaxで行いましょう
* 地図（google map)を表示させましょう

### むずかしめ
* インクリメンタルサーチを入れましょう
* vue or reactでフロントを書きましょう

enjoy !