久しぶりにRailsで開発して感じたこと
数年ぶりにガッツリRailsで開発するプロジェクトに参画している。その中で感じたことを徒然なるままに書こうと思う。なお、「それRailsじゃなくてRubyの話っしょ?」という内容も多分に含んでいると思うが、そこはご勘弁。
久しぶりにRailsで開発して感じたこと Read More »
数年ぶりにガッツリRailsで開発するプロジェクトに参画している。その中で感じたことを徒然なるままに書こうと思う。なお、「それRailsじゃなくてRubyの話っしょ?」という内容も多分に含んでいると思うが、そこはご勘弁。
久しぶりにRailsで開発して感じたこと Read More »
認証エラーの例外が山ほど発生するというもの。
状況としては、次のようなWebアプリだった。
で、ログイン済の状態でこれらのページにアクセスすると、アプリ自体は予期した通りに動くのだが、Slackに例外が上がってくる。アクセスする度に上がってくるので非常に鬱陶しい。
もちろんこれは私がSlackを使ってなくても、ログには同じ例外が吐き出されているので誰にでも起こりうる話。
はじめにこの例外を見たとき、一体自分以外に誰がアクセスしてくるんだ?と思いSlackを確認したが、slack_notifierのデフォルト設定ではUser-agentの値は通知してくれない模様。そこで、application_controller.rbを次のように変更した。
class ApplicationController < ActionController::Base ... before_action :set_notification ... private def set_notification request.env['exception_notifier.exception_data'] = { http_user_agent: request.env['HTTP_USER_AGENT'] } end ... end
そしてページにアクセスして例外を発生させる。Slackを見てみると、次のようにDataが追加されている。
Mediapartners-Googleとな? これを早速ググってみると、どうやらGoogle Adsense用のクローラーであり、ページに最適な広告を選定するため、Adsenseが貼られているページにアクセスしてくるらしい。詳しくはコチラ。
こいつがAccessDenied例外を発生させている原因。Googleのヘルプを読むとrobots.txtでクロールを禁止できるが、禁止すると広告は表示されなくなるらしい。今回の要件としては別に広告を表示する必要もなかったので、robots.txtを編集してみる。・・・がそれでもクロールが止まらない!
その後Googleのヘルプをよくよく読むと、User-agent: *では、Mediapartners-Googleのクロールは止められないらしい。ちゃんとUser-agent: Mediapartners-Googleと指定しなさいと書いてあるじゃないか・・・。
といわけで、次のようにrobots.txtを変更したら、クロールが止まった。
User-agent: * Disallow: /admin/ User-agent: Mediapartners-Google Disallow: /admin/
ふー。ハマっちまったぜ。。。ま、普通は管理ページに広告貼る人はあまりいないと思うので、わりとレアなケースだったのかもしれない。
認証が必要なページにAdsenseを貼る場合の注意点 Read More »
たとえばUserクラスとConfigクラスがあったとして、それぞれ次のような関連を持ってるとする。
class User < ApplicationRecord has_one :config end class Config < ApplicationRecord belongs_to :user end
各ユーザーが各々設定を持つというシチュエーションを想定している。で、この場合に親のファクトリをcreate(:config)のような感じで作るときに、まとめてUser側の属性も設定したいということ。
通常の書き方だと、Configのインスタンスを得るにはこんな感じになるはず。(たぶん)
config = create(:user. name: '...', email: '...', config: build(:config, attr1: '...', attr2: '...')).config
それを次のように書けるようにする。
config = create(:config, attr1: '...', attr2: '...', user: { name: '...', email: '...' })
「なんだ、ほとんど変わんねえじゃねーか」とお思いになるかもしれない。しかし前者は、Configのインスタンスが欲しいんだということがひと目で分からない。また、build :config してるのもいささか冗長だ。後者の方がテストの関心がConfigにあることが一目瞭然だし、タイプ量も少なくてすむ。細かいことだけど、こういうのって結構大事だと思うんだな。
で、肝心のファクトリの定義だが、私はこんな感じに定義している。
FactoryGirl.define do factory :config do attr1 '...' attr2 '...' transient do user { {} } end after(:build) do |config, evaluator| config.user = build(:user, evaluator.user) if evaluator.user.present? end end end
userをtransientにしておきuserの属性(Hash)を渡してもらう。after(:build)の中でuserのファクトリに横流してbuildしているのでuserのファクトリ定義を上書きすることができるというわけ。なお親から子の属性を設定する場合も同様の方法が使えると思う。
これがベストの解かは分からない。もっといい方法があれば教えて欲しいッス。
【factory_girl】子のファクトリ生成時に親の属性をまとめて設定する Read More »
まずは rails 5 のコードから。次のコードを適宜編集して、config/initializersの下に置く。
unless Rails.env.production? reloader = Rails.application.config.file_watcher.new(['path_to_file']) do # do_something end Rails.application.reloaders << reloader ActiveSupport::Reloader.to_prepare do # reloader.execute <=他のファイルが更新されても実行するならこちら reloader.execute_if_updated end end
なにをしているかというと、まずRails.application.config.file_watcherでファイルの更新監視クラスを取ってきている。普通はconfig/environments/development.rb
には次のような設定がしてあるはずなので、
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
ActiveSupport::EventedFileUpdateCheckerクラスが取得できるというわけだ。こいつに監視対象のファイルパスを渡し、ブロック内に実施したい処理を書けばよい。
次に、ActiveSupport::Reloader.to_prepare の部分だが、何らかのファイルに更新があった次のリクエスト処理時に、このブロックが実行されることになっている。ここで先ほど生成したreloaderを実行してやればよい。
reloader.execute_if_updatedというのは監視対象のファイルが更新された場合にだけreloaderを実行する。通常はこのメソッドを使えばよいと思う。他のファイルが更新された場合でもreloaderを実行したい場合はreloader.executeだ。
基本的にはrails 5と同じなのだが、少しだけ使用するクラスが違う。
unless Rails.env.production? reloader = ActiveSupport::FileUpdateChecker.new(['path_to_file']) do # do_something end Rails.application.reloaders << reloader ActionDispatch::Reloader.to_prepare do reloader.execute_if_updated end end
やってることはrails 5と同じなので、説明は省略。
ymls = Dir.glob("#{Rails.root}/app/dsls/config/search/*.rb") ymls.each { |yml| load yml } unless Rails.env.production? ymls.each do |yml| reloader = Rails.application.config.file_watcher.new([yml]) do load yml end Rails.application.reloaders << reloader ActiveSupport::Reloader.to_prepare do reloader.execute end end end
実際に私が使用している例。自作DSLがyamlファイルを読み込むのだが、yamlを変更する際にサーバを再起動しないでいいように、上のようにしてみた。
もともとは、settingslogicという定数管理用のgemを使用していたのだが、yamlを変更するとサーバを再起動しないといけないのが嫌で、解決策を探してみた。
ちなみに拙作の定数管理gemは、サーバを再起動しなくても、定数を記述するyamlを変更したら自動でリロードするようにしてある。
その中のここのソースでrails4と5を切り替えてreloaderを登録するようにしているので、gemを作る人なんかには参考になるかもしれない。
【Rails】リロード時に特定の処理を実行する Read More »
基本的にはCompatibilityのページに書いてあるとおりだが、順を追って説明していく。なおこれはCompatibilityのページのSolution #2に該当する。
まずはTwitterボタンのページでサイトに貼り付けたいボタンのソースをゲットする。通常はそいつをボタンを表示させたい箇所に貼り付ければいいわけだが、turbolinksを使用する場合は、ちょっと違う。取得したソースはaタグとscriptタグがあると思うのだが、そのうちaタグの方だけを貼り付けるようにする。下は@XXXXユーザーのフォローボタンの例。
<a href="https://twitter.com/XXXX" class="twitter-follow-button" data-show-count="false" data-size="large">Follow @XXXX</a>
ボタンを貼り付けたらCoffeeScriptを作成する。ファイル名は任意。
twttr_events_bound = false $ -> loadTwitterSDK -> bindTwitterEventHandlers() unless twttr_events_bound bindTwitterEventHandlers = -> $(document).on 'turbolinks:load', renderTweetButtons twttr_events_bound = true renderTweetButtons = -> $('.twitter-share-button').each -> button = $(this) button.attr('data-url', document.location.href) unless button.data('url')? button.attr('data-text', document.title) unless button.data('text')? twttr.widgets.load() loadTwitterSDK = (callback) -> $.getScript('//platform.twitter.com/widgets.js', callback)
これだけ。
なお本家Compatibilityのサイトと若干CoffeeScriptのソースが異なるので注意。本家版はgetScriptに時間がかかった場合にエラーが出るようになっていたので、それを修正した。(PR出してるけどマージされるかは不明)
turbokinks 5、いまのところすごくいい感じ。パフォーマンスの向上を肌で感じることができてる(気分。)
turbolinks 5 にtwitterボタンを対応させる Read More »