廃墟

本ブログは更新を終了しました。 技術的な記事のみ、有用性を鑑みて残しておきます。

Sunspot (RailsでSolrによる検索を動かすGem)をとりあえず日本語検索に対応する方法

日本語で割と最近っぽい情報が全然なかったので簡単にメモ。

Solr: 6.6.0 / 7.0.0

Sunspot: 2.2.7

<RAILS_APP>/solr/configsets/sunspot/conf/schema.xml の中で <fieldType name="text" から始まって </fieldType> で終わる部分を以下の内容に差し替える。

<fieldType name="text" class="solr.TextField" omitNorms="false" positionIncrementGap="100" autoGeneratePhraseQueries="false">
      <analyzer>
        <tokenizer class="solr.JapaneseTokenizerFactory" mode="search"/>
        <filter class="solr.JapaneseBaseFormFilterFactory"/>
        <filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt" />
        <filter class="solr.CJKWidthFilterFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt" />
        <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>

stopwords_ja.txt 及び stoptags_ja.txt は solrをapache.orgから solr-7.0.0-src.tgz みたいなファイルをダウンロードしてきて 、その中にある solr-7.0.0/solr/core/src/test-files/solr/configsets/_default/conf/lang のうち _ja.txt で終わるファイルを <RAILS_DIR>/configsets/sunspot/conf/lang の中に入れれば良いみたい。 面倒ならとりあえずそれが必要な行を削っても、それなりには動く。

これをベースにtokenizerにuserDictionaryとか入れればユーザ辞書を使った検索とかも出来るっぽい。

今あまり時間がなくて雑なメモになってますが、もしもう少し丁寧に知りたい人がいたらTwitterのメンションとか、ここへのコメントくれれば追記します。

CloudFront配下にRailsを置いたときにSSL対応するMiddleware

最近CloudFront配下にアプリケーションサーバーを置くのが徐々に流行ってる気がします。

想定する構成としては、ブラウザが直接叩くのがCloudFrontで、その直下にELB、そのさらに下に直接pumaというような形式。 nginx使わないパターンですね。 assets以下はどのみちCloudFrontでキャッシュされるから、nginxで高速化するまでもないっていうのがこの設計の思想かなと思ってます。 pumaが重くならない程度に同時接続数を制御する役割もELBがになってくれますし。

さて、ともかくそんな構成にすると、SSL対応で手間取ったのでメモ。

バージョンは以下の想定です。

  • Rails 5.1.2 (4系でも変わらないとは思います)
  • Puma 3.7

困ること

何が困るかというと、CloudFrontでSSL→HTTPに変換して、ELB以降はずっとHTTPで取り回すと、X-Forwarded-Protoヘッダが渡ってこない事です。CloudFrontは何故か独自のCloudFront-Forwarded-Protoというヘッダを渡してきます。

これによって、 force_ssl! も動かないし、そもそも post,put,patchリクエストはCSRFをブロックする機構によって弾かれてしまいます。

フロントがELBだったら問題ないんですが、ここでは最前面にCoudFrontを置く想定…。

ここで対策は2つあります。

  • 1: ELBにもSSL鍵を指定して、CloudFrontからELBへの通信路をSSLにする
    • AWS内しか通らない暗号化/解除の処理がムダでは?
    • ↑ 「AWS内しか通らない」保証はない気がするのでホントに高いレベルのセキュリティを意識するなら怖いかも(2019/5/30追記)
  • 2: どうにかしてCloudFront-Forwarded-ProtoをRailsから認識させる。

ここでは後者を採用しました。

やったこと

以下のRack Middlewareを用意します。

# lib/cloud-front-header.rb
class CloudFrontHeader
  def initialize(app)
    @app = app 
  end 

  def call(env) 
    cf_proto = env['HTTP_CLOUDFRONT_FORWARDED_PROTO'].to_s
    if cf_proto && cf_proto.length > 0
      env['HTTP_X_FORWARDED_PROTO'] = cf_proto
    end

    @app.call(env)
  end 
end

そして、 application.rb に以下の内容を足します。 他にmiddleware使ってない場合は、どこに書いても問題ないと思います。

# config/application.rb
module MyApp
  class Application < Rails::Application
    # 前略
    require './lib/cloud-front-header.rb'
    config.middleware.use CloudFrontHeader
    # 後略
  end
end

と、強引な方法ではありますが、 CloudFront-Forwarded-Proto を勝手に X-Forwarded-Proto に上書きするという荒技でどうにかしたのでした。

Rack Middleware がバグるのは鬱陶しいので、to_s等を使った防衛的なコードになってますが、気に入らない人は適宜書き換えると良いです。

あと、置き場所を考え直すと require は要らないかも知れないです。 もし良い方法あったら誰かコメントとかで教えてください。

こういう事が出来るのはRackの優れたレイヤアーキテクチャのたまものですね。

Rails5で簡単にモバイル/PCのビュー分岐を行う

最近、仕事で必要だったので。 だいたい以下の記事を元にしてるだけですが、ちょいサンプルコードなど含めて解説。

stackoverflow.com

元々は mobylette というgemを使おうとしたらRails5では(たぶん4でも?)コケちゃうので自前実装気味に済ませたって経緯がある。

想定するサービス

基本はレスポンシブだけどTOPページ等の一部の画面だけPC/モバイルでの分岐をしているようなサービス

方法

まず、以下のconcernsを作る。

# app/controllers/concerns/respond_to_mobile_requests.rb

require 'active_support/concern'

module RespondToMobileRequests
  extend ActiveSupport::Concern

  # Regexp From: https://gist.github.com/dalethedeveloper/1503252/931cc8b613aaa930ef92a4027916e6687d07feac
  MOBILE_REGEXP = /Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/  
  included do
    before_action :variant_mobile

    def variant_mobile
      # The solution from: https://stackoverflow.com/questions/39495834/mobile-view-in-rails-5
      request.variant = :mobile if is_mobile_request?
    end

    def is_mobile_request?
      @_is_mobile_request ||= MOBILE_REGEXP.match request.user_agent
    end
  end

end

次に、描画を振り分けたいコントローラおよびそこのアクションで以下のようにする

# app/controllers/home_controller.rb

class HomeController < ApplicationController
  include RespondToMobileRequests

  def index
    if is_mobile_request?
      # モバイルの時だけの処理
    else
      # PCの時だけの処理
    end
    
    # 以下のコードでビュー振り分け
    respond_to do |format|
      format.html.mobile
      format.html
    end
  end
end

あとはビューファイルとして index.html.erb (※hamlでもslimでもご自由に)と index.html+mobile.erb を作れば、勝手にビューを振り分けてくれる。

もしCloudFrontのカスタムヘッダ( CloudFront-Is-Mobile-Viewer 等)とかを使いたければ、request.user_agentにmatchかけてる部分の代わりに request.headers['CloudFront-Is-Mobile-Viewer'] 等を見ればいいと思う。

rails + sunspot なアプリケーションの本番環境をdocker-composeでつくる

Dockerやdocker-composeの話って結構「いいよ」と目にするんですが、いざ始めようと思うとハードルが高いですよね。

僕も今日は丸一日Docker(-compose)に吸われてしまいましたが、その成果をちょっとメモしておきます。

やりたいこと

やりたいのは以下のような構成。

  • Railsが動くコンテナをDockerで作る
  • Sunspotによる検索をしたいのでsolrをDockerで動かす
  • DBはRDSを想定するのでコンテナでは作らず外部に繋ぐ

想定環境

  • Mac OSX Sierra (10.12.4)
  • Docker 17.06.1-ce
  • docker-compose 1.14.0

DBをローカル宛にする方法

まず簡単な方から。

これは、単に docker.for.mac.localhost というホスト名でホストOSが見えるようになっているので、以下のようにすればOK。

# .env.production (抜粋)
DATABASE_URL=mysql2://root:@docker.for.mac.localhost:3306/my_app
# database.yml (抜粋)
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000
  username: root
  password: ''
  socket: /tmp/mysql.sock
production:
  <<: *default
  database: my_app
  # ↓ ここが重要。他はデフォルト。
  url: <%= ENV['DATABASE_URL'] %>

この場合のDockerfile/docker-composeは後述のsolrを使う場合のから読み取って欲しい。

SolrをDockerベースで動かしつつsunspotと連携させる方法

こっちが難しかった。

先にコードを示す。

#Dockerfile
FROM ruby:2.4.1
RUN apt-get update -qq && \
  apt-get install -y build-essential libpq-dev nodejs
RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle config build.nokogiri --use-system-libraries && \
  bundle install -j3 --without test development --no-cache
COPY . /app

こっちは割と普通の例だと思う。 bundle install--deployment を付けるとrakeがないって怒られるようになってしまったので、つけるのやめた。

# docker-compose.yml
version: '2'
services:
  web:
    restart: always
    build: .
    env_file: .env.production
    command: "bundle exec rails s -p 3000 -b '0.0.0.0' -e production"
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - solr
  solr:
    image: solr:6.6.0
    volumes:
      - ./solr/configsets/sunspot/conf:/opt/solr/server/solr/production
      - solr:/opt/solr/server/solr/production/data
    entrypoint:
      - docker-entrypoint.sh
      - solr-precreate
      - production
      - /opt/solr/server/solr/configsets/sunspot

volumes:
  solr:

何のことはなく、volumesでホストOSのディレクトリをゲストに使わせてるだけなんだけど、これが重要だった。

この状態で docker-compose build && docker-compose up -d で立ち上げた後、solrの Core というのを作るために次のコマンドを打つ

# このコマンドは打たなくてよくなった
# docker-compose exec solr bin/solr create_core -c production

↑entrypointのセクションで不要に出来ました。

solrのwebコンソールとかにらめっこしながら、とにかく開発時にsunspotが生成する schema.xmlsolrconfig.yml をdockerのsolrに認識してもらおうとしてたらこのようになった。

現状の課題

現状だと solr/configsets/sunspot/conf/core.propertiessolr/configsets/sunspot/conf/data/ にsolrのデータが生成されてしまう。 開発環境で邪魔になるという問題もあるし、本来これらはvolumeを定義してそこに保存されて欲しい。 しかし、solrのconfig類を渡す必要もあるので、いまひとつどうすればいいか分からなかった。 ……solrについては元々あまり詳しくないので、誰か補足してくれたら凄く嬉しいです。是非コメントください。

参考

以上、調べるのに割と苦戦したのでメモでした。誰かの助けになれば幸い。

科学×中学生がテーマなノベルゲーム「EVERETT EFFECT」 開発途上版 "interpretation" 公開予定

f:id:akn_ep:20160729005618j:plain

everett-effect.com

僕がシナリオとか演出とか企画全体の概ね半分くらいをやっている「EVERETT EFFECT」というゲームの開発途上版を5月6日に東京ビッグサイトで行われるコミティア120にあわせて公開します。

ノベルゲーム部公式ページ

ノベルゲーム部、という名前のコミティア部活動の一員としての出展です。

ブースは K04b Sapience です。よろしくおねがいします。

Mac OSXでRPGツクール2000製のゲームを遊ぶ(EasyWine+IPAモナーフォント)

RPGツクール2000や2003の時代、名作フリーゲーム多いですよね。

久しぶりにやりたい作品があったので、手元のMacで動くようにしてみました。

ちなみに、クリアまでやった訳じゃないので、もしかしたらセーブデータの扱いとかでおかしくなることがあるかもしれないです!

2003とかでも似たような感じで動く気がします。まだやってないのですが、上手く行った/行かなかった人がいたらコメントください。

1. EasyWineのインストール

以下のページを参照。 NAVERまとめが公式配布先っていうちょっと変わり種ですね。

🍎 EasyWine.app 🍷 - 😃 mattintosh note 📝

2. RPGツクール2000RTPのインストール

2000時代はRTP同梱は稀ですよねー…ってことで。

RTPをダウンロードして、インストーラーのexeを開くだけです。

もしexeが他のソフトに関連付いてしまってる場合は、副ボタンクリック(二本指とかCtrl+クリックとか)からeasywineを選んで開く。

3. IPAモナーフォントのインストール

以下のサイトからIPAモナーフォントを持ってきてインストール

各ファイルを開くとFontBookが開くので、流れに従えばインストールできるかと。

IPAモナーフォント

4. user.cfg の編集

レジストリ編集。しないと文字が汚くて遊べたもんじゃない。

参考サイトによると、IPAモナー明朝は下が途切れちゃうから明朝もゴシックにした方がいいとあり、 やってみたら確かに途切れちゃったので僕もゴシックにしてみました。

ここのやり方は、以下の通り。

  1. Finderで適当なウィンドウを開いて Cmd+Shift+G でフォルダ移動ダイアログを開く
  2. ~/Library/Caches/Wine/prefixes/default/ と入れて開く
  3. user.cfg というファイルがあるので、CotEditorなどで開く
  4. [Software\\Wine\\Fonts\\Replacements] と書かれたエリアに以下の内容を書き足す
"MS Gothic"="IPA \x30e2\x30ca\x30fc \x30b4\x30b7\x30c3\x30af"
"MS Mincho"="IPA \x30e2\x30ca\x30fc \x660e\x671d"
"MS PGothic"="IPA \x30e2\x30ca\x30fc P\x30b4\x30b7\x30c3\x30af"
"MS PMincho"="IPA \x30e2\x30ca\x30fc P\x660e\x671d"
"MS UI Gothic"="\x30d2\x30e9\x30ae\x30ce\x4e38\x30b4 Pro W4"
"\xff2d\xff33 \x30b4\x30b7\x30c3\x30af"="IPA \x30e2\x30ca\x30fc \x30b4\x30b7\x30c3\x30af"
"\xff2d\xff33 \x660e\x671d"="IPA \x30e2\x30ca\x30fc \x30b4\x30b7\x30c3\x30af"
"\xff2d\xff33 \xff30\x30b4\x30b7\x30c3\x30af"="IPA \x30e2\x30ca\x30fc P\x30b4\x30b7\x30c3\x30af"
"\xff2d\xff33 \xff30\x660e\x671d"="IPA \x30e2\x30ca\x30fc P\x660e\x671d"

尚、念のためuser.cfgのバックアップを取っておいた方が安心です。

参考:

参°ぼっくす: Mac上のWineでRPGツクールのゲームを動作させてみる

🎮 Mac で『Ib(イヴ)』をプレイする 🍎 - 😃 mattintosh note 📝

GAE/Goの基本的な設定の覚え書き

Go言語は昔(1.4まで?)はパッケージ管理の標準的な仕組みがなくて、1.7あたりでvendorディレクトリを必ず読むようになったらしい。 デファクトスタンダードは、今の所glideというものらしい。 godepsも良いみたいだけど、個人的に何となくglideの方が扱いやすく思えたので。

GOPATHを指定する必要があり、僕はautoenvを使う事にしたけど、何にせよこの後のプロセスを踏む前に必ず、作業ディレクトリがGOPATHに含まれてるようにする。

echo $GOPATH
/Users/akn/go:/Users/akn/Documents/TimeCard/time-card-gae

普通の $PATH と同様 : で区切っていいらしい。

で、とにかくディレクトリ構造が肝心。僕はこんな風にした。(srcなしとかも試したけど、srcがないと上手く動作しなかった)

your-app-dir
└── src
    ├── app.yaml
    ├── glide.lock
    ├── glide.yaml
    ├── main.go
    ├── server
    │   └── server.go
    └── vendor

主要なファイルの中身を晒しておく。

app.yaml

application: my-app
version: 0
runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app

nobuild_files:
- vendor

skip_files:
- vendor/.*$

main.go

package main

import (
    "server"
)

func init() {
    server.Start()
}

良く分かんないんだけど、main.goから直接vendor以下のを読みに行くと上手く行かなかったので、server packageを定義して、そっちから読むようにしてる。

あとは以下で開発

goapp serve

もしくはデプロイ

goapp deploy

これだと覚え書きすぎて分かりにくいので、丁寧めの記事へのリンクを貼っておきます。 ただし、僕は goenv を使って app/srcでディレクトリを分ける方法はCan’t findとか言われてライブラリが読まれず、上手く行かなかった。

www.freegufo.com

qiita.com

qiita.com