2018年12月17日月曜日
【備忘録】Visual Studio Codeで文字検索範囲が広すぎるときに疑う箇所
こんにちは!
シタテル株式会社エンジニアのいしづかです。
みなさんはコードを書く時、どんなエディタを使ってらっしゃるでしょうか。
現在開発が続いているシタテルコントロールシステムのバックエンドはRailsで書かれています。
開発メンバーはそれぞれ自分が合うエディタを使っています。Ruby Mine使っている人もいますし、Atomもいたような・・・。
わたしはVisual Studio Codeを使っています。え?Vimであらずは人であらず?いや、まぁ、えへへ。
コードを書いているとコード内検索をかけたくなります。その際にはnode_modules配下とかは最初から外しておきたいものです。
今回はVisual Studio Code限定ですが、除外設定でハマったので備忘録としてその内容を書こうと思います。
Visual Studio Codeの除外設定
Visual Studio Codeの除外設定は大きく2つあり、 Files Exclude と Search Exclude があります。
Files Excludeにパスを登録すると、その配下はエクスプローラー上にすら表示されません。Visual Studio Codeからは完全に管理対象外となり、当然検索にも引っかからなくなります。
Search Excludeにパスを登録すると、エクスプローラー上には表示されますが、検索に引っかからなくなります。
よって、検索の除外対象は「Files Exclude」+「Search Exclude」ということになります。
わたしがSearch Excludeに登録しているのは
の4つです。もうちょっと増やしてもいいかなと思うんですが、必要最低限で運用しています。
設定完了であるはずが、、、アレ、logの中身とか検索対象になってる。。。
パターンの書き方が間違っているのか?
設定する箇所が間違っているのか?
アレコレ試した挙げ句、最終的にたどり着いたのは
このボタンがONになっていないだけでした。。。
デフォルトではONでなにかのときにOFFにしちゃっていたのかもしれませんが、こんなの気づかないよVisual Studio Codeさん。。。
まとめ
何かを検索したとき、ほしいものがほしい分だけパッと手に入るっていいですよね。
検索範囲を適度なものに調整しておくのはコードを書くときのストレスを軽減してくれると思います。
かく言うわたしが、この除外設定が効いていなかった数十分間はストレスフルな状態でした。
他の開発メンバーがどんなチューニングを行っているかちょっと気になるところです。どんなプラグイン入れているかとか聞けたらレポートしたいと思います。
2018年12月11日火曜日
Active Storage移行記:データ移行編
こんにちは、あさのです。
前にActive Storageの記事を書きましたが、今回もそのネタで行こうと思います。
Active Storage移行記:バリデーション編|sitateru tech blog
今回は、Paperclip管理のデータをActive Storage管理に移行したときのお話です。
データ構造
Paperclipではファイルのデータは各モデルのカラムに保存されています。
userクラスのiconというファイルであれば、userテーブルに以下のようなカラムがあります。
t.string "icon_file_name", comment: "ファイル名"
t.string "icon_content_type", comment: "ファイルのcontent type"
t.integer "icon_file_size", comment: "ファイルサイズ"
t.datetime "icon_updated_at", comment: "ファイル更新日時"
対してActive Storageでは全クラスの全ファイルをまとめて2つのテーブルで管理します。
create_table "active_storage_attachments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
create_table "active_storage_blobs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.bigint "byte_size", null: false
t.string "checksum", null: false
t.datetime "created_at", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
たとえば、
- userクラスのicon
- user.id=15
- ファイル名 prof.jpg
というファイルだと以下のようなレコードになります。
2つのテーブルは blob_id
で関連づけられるんですね。
id name record_type record_id blob_id created_at
15 icon User 3 16 2017-12-28 19:23:03
id key filename content_type metadata byte_size checksum created_at
16 3e0ca647-18e3-43ee-85c4-4de552bac46f prof.jpg image/jpeg {} 44202 isFyYNJIs/6pyptfRUMUlA== 2017-12-28 19:23:03
データ移行
さて、それぞれのデータ構造がわかったのでマイグレーションをしていきます。
このあたりの記事を参考にさせてもらい、rakeタスクで実装しました。
namespace :import_to_active_storage do
desc 'Import to ActiveStorage'
task run_all: :environment do
models = ActiveRecord::Base.descendants.reject(&:abstract_class?)
import_data(models)
end
private
def import_data(models)
conn = ActiveRecord::Base.connection
models.each do |model|
attachments = model.column_names.map do |c|
c =~ /(.+)_file_name$/ && Regexp.last_match(1)
end.compact
model.find_each do |instance|
attachments.each do |attachment|
puts "Process #{model.name}/#{instance.id}"
next unless instance.send("#{attachment}?")
next if conn.select_one("SELECT `id` FROM active_storage_attachments WHERE `record_type` = '#{model.name}' AND `record_id` = #{instance.id}").present?
puts "Migrate #{instance.send(attachment.to_s)}"
ActiveRecord::Base.transaction do
execute_statement(conn, model, instance, attachment)
end
end
end
end
end
def execute_statement(connection, model, instance, attachment)
active_storage_blob_statement(connection).execute(
key,
instance.send("#{attachment}_file_name"),
instance.send("#{attachment}_content_type"),
instance.send("#{attachment}_file_size"),
checksum(instance.send(attachment)),
instance.updated_at.strftime('%Y-%m-%d %H:%M:%S')
)
active_storage_attachment_statement(connection).execute(
attachment,
model.name,
instance.id,
instance.updated_at.strftime('%Y-%m-%d %H:%M:%S')
)
end
def active_storage_blob_statement(connection)
connection.raw_connection.prepare(<<-SQL)
INSERT INTO active_storage_blobs (
`key`, `filename`, `content_type`, `metadata`, `byte_size`, `checksum`, `created_at`
) VALUES (?, ?, ?, '{}', ?, ?, ?)
SQL
end
def active_storage_attachment_statement(connection)
connection.raw_connection.prepare(<<-SQL)
INSERT INTO active_storage_attachments (
`name`, `record_type`, `record_id`, `blob_id`, `created_at`
) VALUES (?, ?, ?, LAST_INSERT_ID(), ?)
SQL
end
def key
SecureRandom.uuid
end
def checksum(image)
uri = URI.parse("https:#{image.url}")
body = Net::HTTP.get(uri)
Digest::MD5.base64digest(body)
end
end
そこそこ長いですが、結局は各モデルの各ファイルごとにPaperclipのカラム情報とファイルのハッシュ値を取り、
Active Storageのテーブルに入れるという繰り返しです。
なお、各モデルの実装をActive Storage用に書き換えた後ではこのタスクは動かないので、
Paperclip時に実行する必要があります。
実際にやったところ数万件のデータがあったために5~6時間かかるという大引っ越しになってしましましたが、
なんとか乗り切りました。
ということで今回はActive Storageのデータ移行について書きました。
まだもう少しActive Storageネタで書けるかもしれません。
それでは。
2018年11月26日月曜日
Active Storage移行記:バリデーション編
こんにちはあさのです。
今回は、Ruby on Railsのファイルアップロード機能をPaperclipからActive Storageに移行したときの話を書こうと思います。
Paperclip
Paperclipはファイルアップロード用のGemです。
https://github.com/thoughtbot/paperclip
比較的導入が簡単であり、アプリケーションにファイルアップロード機能をつけるためによく使われています。
ですが、現在は "Deprecated" とされていて、Rails 5.2から標準機能の1つとなったActive Storageへの移行が推奨されています。
Active Storage
Active StorageはRailsのバージョン5.2から標準搭載されている機能の一つで、Paperclipと同様Active Recordのオブジェクトと紐づけたファイルアップロード機能を提供しています。
https://github.com/rails/rails/tree/master/activestorage
シタテルでもRails 5.2へのアップグレードが落ち着いたところでこの移行を行いました。
そんなわけで、移行にあたっていくつか注意点となったところを振り返っていきたいと思います。
今回はモデルのバリデーションの話です。
Active Storage でのバリデーション
Papaerclipにはバリデーション機能があります。
以下はiconという名前で扱う添付ファイルのバリデートをする例で、モデル内にこう書いておけば保存時に自動でチェックされます。
見てのとおりですが、ファイル形式は[jpg, jpeg, gif, png]のどれか、ファイルサイズが10MB以下である必要があります。
validates_attachment :icon,
content_type: {
content_type: [
'image/jpg',
'image/jpeg',
'image/gif',
'image/png'
]
},
size: {
less_than_or_equal_to: 10.megabytes,
message: I18n.t('errors.messages.file_too_large')
}
しかしActive Storageにはこのようなバリデーション機能はありません。
じゃあどうするんだ!という話ですが、素直にActive Recordのカスタムバリデーションを使いましょう。
これで同じようなことができます。
validate :validate_icon
def validate_icon
return unless icon.attached?
if icon.blob.byte_size > 10.megabytes
icon.purge
errors.add(:icon, I18n.t('errors.messages.file_too_large')
elsif !image?
icon.purge
errors.add(:icon, I18n.t('errors.messages.file_type_not_image'))
end
end
def image?
%w[image/jpg image/jpeg image/gif image/png].include?(icon.blob.content_type)
end
ちなみに、
attached?
はファイルが存在するかどうかblob.byte_size
はファイルサイズblob.content_type
はファイルタイプ
を取得しています。
ということでActive Storageのバリデーションについて書いてみました。
Paperclipからの移行はいろいろと作業があったので他のトピックについてもまた書いていきたいと思います。
それでは。
2018年11月8日木曜日
Ruby+Nokogiri 5分で作るスクレイピングツール
マーケティングチームからページャー付きのWEBサイト全ページのhtmlが欲しいと言われましたので、簡単なRuby+Nokogiriスクレイピングコードを書きました。
今回は、fromAnaviさんのページから、弊社渋谷オフィス周辺のアルバイト情報のhtmlをGetしてみます!
Nokogiriをインストール
gem install nokogiri
コーディング
scraping.rbrequire "open-uri"
require "nokogiri"
file = File.open("froma.txt", "w")
(1..117).each do |page|
charset = nil
doc = Nokogiri::HTML(open("https://www.froma.com/baito/jobList/?s_area_cd=1i2002&s_area_cd=1i2004&sort_cd=&edition_cd=1&shrt_indx_cd=1002&st=08&page=#{page}"))
file.puts(doc.xpath("//div[@class='main']").to_html)
end
file.close
それでは取得してみます 🚀
$ ruby scraping.rb
出力結果froma.txt
<div class="main">
<ol class="breadcrumbs" itemprop="breadcrumb">
<li><a href="/index.html">全国のアルバイト/バイト</a></li>
<li><a href="/P01/">関東</a></li>
<li>渋谷駅周辺、原宿/表参道の仕事情報を探す</li>
</ol>
<!-- インクルードエリア11(編集用上エリア) -->
<!-- /インクルードエリア11(編集用上エリア) --><h1 class="hd-basic-1">「いろいろな条件」のアルバイト/求人情報</h1>
<table class="scp scp--job-list">
<colgroup>
<col style="width:96px;">
<col style="width:131px;">
<col style="width:96px;">
<col style="width:406px;">
</colgroup>
<tr>
<th>勤務地</th>
<td colspan="3" class="scp-area">
<div class="scp-area-main">
<ul class="scp-area-main__txt">
<li>渋谷区 > 渋谷駅周辺、原宿/表参道</li>
</ul>
<div class="scp-area-main__btn"><a href="/baito/ajax/jobSearchCond/doAreaCond?st=08&sort_cd=&shrt_indx_cd=1002&s_area_cd=1i2002&s_area_cd=1i2004&edition_cd=1" class="btn-panel--main jsc-modal-trigger-area" onclick="sendSC_ListLimitPanel('1000');">エリアを選ぶ</a></div>
</div>
<div class="scp-area-sub">
<div class="btn"><a href="/baito/ajax/jobSearchCond/doAlongRailCond?st=08&sort_cd=&shrt_indx_cd=1002&s_area_cd=1i2002&s_area_cd=1i2004&edition_cd=1" class="btn-panel--sub jsc-modal-trigger-rail" onclick="sendSC_ListLimitPanel('2000');">沿線・駅を選ぶ</a></div>
</div>
</td>
(長いので割愛)
無事に取得できました 🍻ハードコーディングだらけですが、即興ということで今回はこここまで。
RubyとNokogiriを使ったシンプルなスクレイピングを紹介しました。