初めまして。chihaと申します。
TECH::EXPERT短期にてRuby/Railsを中心に学習を開始して1ヶ月半ほど経ちました。
いい加減技術的アウトプットもしなければ、ということで初のQiita投稿です。
初めに
当記事は私(chiha)が初めて個人で開発したアプリケーションをデプロイしたので、用いた技術やコードについての記録を残すために書いています。
概要
アプリケーション名:FishingRecords
URL:https://fishing-records.herokuapp.com/
アプリ概要:ユーザー認証機能、投稿機能(釣果、釣り場)、投稿検索機能、詳細表示機能(googlemap付き)
アプリケーショントップページにて概要を表示しています。
使用ツール
- Ruby on Rails 5.2.2
- Ruby 2.5.1
- jQuery
- 開発・テスト MySQL
- 本番 PostgreSQL
- デプロイ heroku
- 本番の画像アップロード Amazon S3
機能とツール、コード
苦労したコードや初めて使用した技術についての記録。
やったこと
- 初期データの実装(スクレイピングをしてCSVへの書き出し、CSVからのcreate)
- selectフォームの非同期通信による連動機能
- herokuでのデプロイ
初期データ
http://yu0105teshi.hateblo.jp/entry/2016/08/03/180603
こちらを参照に、lib/tasks/seed.rakeを作成し、以下のコードを記述してseedコマンドでファイルを指定して走らせることができるように。
Dir.glob(File.join(Rails.root, 'db', 'seeds', '*.rb')).each do |file|
desc "Load the seed data from db/seeds/#{File.basename(file)}."
task "db:seed:#{File.basename(file).gsub(/\..+$/, '')}" => :environment do
load(file)
end
end
これで、db/seeds以下に配置した***.rbファイルに対して
$ rake db:seed:***
でファイルを指定しながらseedを叩けるようになりました。
スクレイピングをして初期データを作るのでnokogiriを導入
Gemfile
gem "nokogiri"
ターミナル
$ bundle install
https://qiita.com/kumamonmaster/items/9bb2aadde56c956fdc9f
https://qiita.com/mukoya/items/e975358ae8b86d64e5b2
この辺りを参考に、某百科事典サイトから魚の名前を取得し、csvに書き出す処理と、csvからseedデータを作成する処理を記述
/db/seeds/scraping.rbを作成し記述
require 'csv'
require "nokogiri"
require "open-uri"
# 取得元のURLを代入
url = "https://ja.wikipedia.org/wiki/%E9%AD%9A%E3%81%AE%E4%B8%80%E8%A6%A7"
contents = []
# スクレイピングでデータ取得
doc = (Nokogiri::HTML(open(url)))
# 開いたurlのhtml内で、tdタグの中のulタグの中のliタグの中のa要素という条件に一致するものに対して繰り返し処理
doc.css("td").css("ul").css("li").css("a").each do |link|
#一致した要素の本文とhref属性の要素を取得して一つの配列化(csvへの書き出しの際、1つの配列が1つの行になるため)
data = [link.content, link[:href]]
contents << data
end
# db/seeds/csvにfish_names.csvを作成させて書き出し
CSV.open("db/seeds/csv/fish_names.csv","w") do |csv|
contents.each do |content|
csv << content
end
end
# CSVからseedデータをcreate
CSV.foreach("db/seeds/csv/fish_names.csv", encoding:"UTF-8") do |row|
fish_name = row[0]
fish_url = row[1]
Fish.find_or_create_by(name: fish_name, url: ("https://ja.wikipedia.org" + fish_url))
end
これで
$ bundle exec rake db:seed:scraping
を叩いて初期データを作ってもらえるように。
こんな感じに魚の名前がズラーっと。
ついでに県と市のデータを作ってもらえるようにコードを書きました。
Prefectureテーブル
Column | Type | Options |
---|---|---|
name | string | null: false |
has_many :cities |
Cityテーブル
Column | Type | Options |
---|---|---|
name | string | null: false |
prefecture_id | references | foreign_key: true |
belongs_to :prefecture |
こんな感じの2つのテーブルにまとめてデータを作ってもらいます。
今回はdb/seeds/csv配下に郵便局の郵便番号一覧(https://www.post.japanpost.jp/zipcode/download.html )のcsvファイルを配置しています。
https://qiita.com/ultrasevenstar/items/bcf1601e2269e46e1e9b
こちらのcsv読み込み以降のコードを参考にしました。
/db/seeds/prefecture.rbを作成して以下を記述
require 'csv'
require "open-uri"
# csvを開くが文字コードが違うので指定してあげる
CSV.foreach("db/seeds/csv/KEN_ALL.CSV", encoding: "Shift_JIS:UTF-8") do |row|
prefName = row[6]
cityName = row[7]
#県のデータを作る
pref = Prefecture.find_or_create_by(name: prefName)
#市のデータを作る
City.find_or_create_by(name: cityName, prefecture_id: pref.id)
end
これで
$ bundle exec rake db:seed:prefecture
を叩いて県と市のデータを作ってくれます。
初期データ関連は以上になります。
selectフォームの非同期通信による連動機能
具体的に言うと、県を選ぶと市のフォームの内容が入れ替わる機能です。
こういうやつ。
http://kawahiro.hatenablog.jp/entry/2013/10/26/233051
こちらのコードを参考に。
テーブルとアソシエーションは先述の通りです。
materialize導入、hamlでの記載をしています
/views/posts/new.html.haml
= form_for @post do |f|
//…中略
.pref-choice-form
.pref-choice-form__left
= f.label :prefecture_id ,"都道府県"
= f.select :prefecture_id, @pref_choices, class: "select_form",include_blank: true
.city-choice-form
.city-choice-form__left
= f.label :city_id ,"市区町村"
= render partial:"cities", locals: {city_choices: @city_choices}
/views/posts/_cities.html.haml
.input-field.col.s12.search_cities
= select :post,:city_id,city_choices,include_blank: true
routes.rb
# 今回はpostsの中で使用するのでposts/cities_selectにしています
resources :posts do
collection do
get :cities_select
end
end
posts_controller.rb
# ルーティングで作ったcities_selectという処理を書く
def cities_select
if request.xhr?
#飛んできたリクエストとパラメータで表示データ生成
@cities = City.where(prefecture_id: params[:prefecture_id])
#selectフォームに出す個別の選択肢用の処理([フォームに表示するテキスト,フォームで飛ばしたいID]という形を作らせる
@city_choices = @cities.map{|city| [city.name, city.id]}
#作った配列データをcityのselectフォーム部分に飛ばす
render partial: 'cities', locals: {city_choices: @city_choices}
end
end
jsフレームワークのjQueryを使用しています
$(window).load(function(){
$("#post_prefecture_id").on("change",function(){
$.ajax({
type: "get"
url: "/posts/cities_select",
data: {
prefecture_id: $(this).val()
}
})
.done(function(data){
$(".search_cities").html(data);
//Materializeのformを使用している場合、これ書かないと処理後のフォームが表示されない
$("#post_city_id").formSelect();
})
});
});
これで完成!
Materializeのselectフォームでは、
$(document).ready(function(){
$('select').formSelect();
});
//https://materializecss.com/select.htmlのinitialization参照
という処理を最初にかけないと表示されないので、ajax処理後に追加したフォームに対して$("#post_city_id").formSelect();みたいにしてあげないと選択肢入れ替え後のフォームが表示されませんでした。
こんな感じで連動selectを実装しました。
herokuでのデプロイ
https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39
こちらの記事そのままです。ありがとうございました。
自分のローカル環境がMySQLだったので曖昧検索のコードが上手く走らなかったので合わせて修正しました(以下の記事と同じ書き方をしていました)
https://qiita.com/smizuka/items/a80bb84191e015b34e40
Post.where("name LIKE(?) AND city_id LIKE(?)","%#{pamrams[:name]}%","#{params[:city_id]}")
こんな感じで書いてたのですがPostgreSQLでは型チェックがより厳密なのでエラーになる(そもそも型を意識しないコーディングが悪い)。
Post.where("name LIKE(?) AND city_id::text LIKE(?)","%#{pamrams[:name]}%","#{params[:city_id]}")
これで本番のPostgreSQLでは動くものの、今度はローカルでエラー。
最終的に
Post.where("name LIKE(?)","#{params[:name]}").where(city_id: params[:city_id])
whereを重ねて書いたら全部解決しました。知識不足。
最後に
長くなるので今回の記事ではこの辺りにします。あくまで導入した新しい技術についての記録を残す感じにしました。
検索機能など、自分で書いたコードについてはおいおい書きます。忘れないようにします。
まだまだ学習2ヶ月ほどのド初心者ですので、より良いコード、間違った点などがあればご教授頂けると幸いです。