はんなり商店

落ち着いた華やかさがあり、上品に明るく陽気なさまを表す。

Bootstrap3のpaginationでtext-algn centerが効かない問題をちゃんと調べてみる

Bootstrap3のpaginationはデフォルトでは左寄せとなっています。

            <ul class="pagination">
                <li class="privious"><a href="#">前へ</a></li>
                <li><a href="#">1</a></li>
                <li><a href="#">2</a></li>
                <li><span>…</span></li>
                <li><a href="#">4</a></li>
                <li><a href="#">5</a></li>
                <li class="next"><a href="#">次へ</a></li>
            </ul>

f:id:sousousore1:20171007133912p:plain   中央寄せにするためには、paginationクラスをつけた要素のwrapperにtext-centerのクラス追加してやれば実現できます。

        <div class="text-center">
            <ul class="pagination">
                <li class="privious"><a href="#">前へ</a></li>
                <li><a href="#">1</a></li>
                <li><a href="#">2</a></li>
                <li><span>…</span></li>
                <li><a href="#">4</a></li>
                <li><a href="#">5</a></li>
                <li class="next"><a href="#">次へ</a></li>
            </ul>
        </div>

f:id:sousousore1:20171007133957p:plain

と、解決するのは簡単ですが、そもそもなぜこういったことが必要なのか、ちゃんと向き合ってみました。

通常、li要素はinline要素なので、ulにtext-align:center;をつけるとul要素の幅に合わせて中央に配置することが可能です。(liの左側についているline-styleはulの幅に合わせて左側に配置されるがその場合はline-style-positionなどをいじる)

ただ、paginationクラスを付けたul要素に単純に text-align:center; を付けてもうまく中央寄せすることはできません。これは、pagination>li>afloat:leftが付いているのでli要素には実質中身がない状態になっているためです。

と、inline要素でもfloatなどで中身がない状態になってしまうとtext-align:centerが効かないことがあるということですね。

今回のBootstrapのケースではpaginationクラスがinline-blockであることから外側からtext-centerを付けて中央寄せすると簡単に中央寄せできるというアプローチでした。

Herokuを眠らせない方法

概要

Google Apps Scriptを利用して30分おきにアクセスを発生させてHerokuを眠らせないようにしています。 複数のサイトを考慮し、またスリープ時間も加味しております。

トリガーについて

アイコンバーの時計のようなアイコンをクリックすると、「トリガーが設定されていません。今すぐ追加するにはここをクリックしてください。」 と表示されると思うのでそちらをクリック。 ”時タイマー”を”分タイマー”にし”30分ごと”を選択して保存をクリックします。

設定するスクリプト

// 起こすサイトの情報
// ※日曜が0、土曜日が6。
var sites =[{
  url       : "https://hogehoge.herokuapp.com",
  wakeupHour: 10,
  sleepHour : 19,
  activeDayOfWeek: [0,1,2,3,4,5,6]
}];

// 現在の日の指定した時間、分、秒を取得します。
function createCurrent(hours, minutes, seconds) {
  hours   = hours   || 0;
  minutes = minutes || 0;
  seconds = seconds || 0;

  var current = new Date();
  current.setHours(hours);
  current.setMinutes(minutes);
  current.setSeconds(seconds);
  return current;
}

// URLアクセスしてHerokuを起こします。
function wakeUp() {
  sites.forEach(function(site) {
    var now        = new Date();
    var wakeupTime = createCurrent(site.wakeupHour);
    var sleepTime  = createCurrent(site.sleepHour);
    var active     = wakeupTime <= now && now <= sleepTime && 0 <= site.activeDayOfWeek.indexOf(now.getDay());
    if (active) {
      UrlFetchApp.fetch(site.url, { muteHttpExceptions:true });
    }
  });
}

お名前.comで取得したnaked domainをherokuのアプリケーションに割り当ててSSLも自動更新で運用する方法

現時点(2017年度9月)でおそらく最短距離で実施する方法だと思います。 昨今SSL接続必須化の流れを鑑み、ドメインを割り当てるだけでなく、SSL接続を可能にし、証明書更新の運用もHerokuに丸投げするように設定してみました。

  1. お名前.comでドメインを取得する
  2. HerokuのDynoをHoby($7/month)以上にする
  3. HerokuのSettings > Domains and certificates
    1. Configure SSLでAutomaticallyを選択しContinueを押下
    2. Add Domainで1.で取得した独自ドメインを設定
  4. HerokuのResources > Add-onsでPointDNSを追加
  5. PointDNSで1.で取得した独自ドメインを登録
  6. PointDNSで登録したドメインのレコード設定でALIAS、CNAMEに3-2で取得されたDNS Targetを設定
  7. お名前.comでネームサーバーの変更で6.で一覧表示されているTypeがNSのもの(ネームサーバ おそらく dns12.pointhq.com.など3つほど)を登録

オープンソースのmastodonからみるRails + React構成や採用されているライブラリについて

mastodonについて

mastodonという新興SNSが国内でも流行りだしています。フェーズとしてはまだイノベータからアーリーアダプターが様子を見てるって感じかなと個人的には思います。

mstdn.jpという日本ドメインインスタンスでは、現時点(2017/4/22)で全インスタンスと比べても世界一のユーザ数を誇っています。ただ、実際に投稿内容を見てみるとユーザもまだテスト投稿したりという感じのようです。(もっとコアなドメインインスタンスだとそのコンテキストの投稿がもっと盛んにされているのかもしれないですが)

このサービスの面白いところは、分散型ネットワーク+OSSというところなのかなと勝手に解釈しています。つまり、個人もしくは企業がTwitterのようなSNSサービスを(手軽に)独自運営していけてそれが本家の改善にもつながる仕組みになっているところに魅力があります。もう少し具体的に言うと本体のソースコードOSSなので大抵はForkしたものをデプロイすることになると思うのですが、そのインスタンス特有の機能を入れて本体とは少し違ったアプリケーションにしたり、場合によっては追加した機能や見つかったバグの修正を本体側にPull-Requestするなどサービス自体が分散して成長していく仕組みが取り入れられているのです。

コードを覗いてみてみる

前置きが少し長くなりましたがコードを実際に見てみるとRails+Reactで構成されていることがわかります。流行りのWebアプリケーションの構成だと思うので、参考になるところが多いです。

サーバーサイド構成

Rubyは2.4.1、Railsのバージョンは現時点で5.0.2で比較的新しい構成であることがわかります。

ruby '2.4.1'

gem 'rails', '~> 5.0.2'
APIの管理

app/controllers/api_controller.rbにApiControllerと言う基底クラスを定義しています。また、app/controllers/api/v1配下に実際のAPIをApiControllerクラスの具象クラスとして配置しています。これでいわゆるAPIのバージョニング管理をしているようです。

concernの実装

model/concerns配下では以下のような共通の振る舞いが定義されています。

cacheable.rb
paginable.rb
streamable.rb
targetable.rb

paginable.rbを見てみると内部的にarel_tableを使用してqueryを組み立てていることがわかります。

scope :paginate_by_max_id, -> (limit, max_id = nil, since_id = nil) {
      query = order(arel_table[:id].desc).limit(limit)
      query = query.where(arel_table[:id].lt(max_id)) unless max_id.blank?
      query = query.where(arel_table[:id].gt(since_id)) unless since_id.blank?
      query
}

フロントエンド構成

app/assets/javascripts/components配下にReactのコードが配置されています。JavascriptはECMAScirpt 2015記法で書かれています。Gemfileをみると以下の記述が確認できます。

gem 'react-rails'
gem 'browserify-rails'
gem 'autoprefixer-rails'
React + Redux

reactをrailsに導入するアプローチはいくつかありますが、mastodonではasset_pipline上にReactをのせるためreact-railsを採用しています。また、browserifyでbabelを使ってコンパイルするようにbrowserify-railsを採用しています。Reactでのスタイル適用はインラインでもscssでもどちらにも書かれています。

Ajax

ajax通信ライブラリとしてはaxiosが採用されています。ただし、使用するにはapi.jsxというラッパーモジュールから呼び出しています。

レンダリング対策

react-addons-pure-render-mixinが採用されています。

途中。気づいたら追記

Azure AppServiceで任意のタイムゾーンを設定する

タイムゾーンを日本に合わせたい人へ

ポータル > AppService > アプリケーション設定 でアプリ設定にWEBSITE_TIME_ZONEを「Tokyo Standard Time」で保存すればアプリが自動的に反映してくれます。

f:id:sousousore1:20160913182548p:plain

ASP.NET Coreでスクレイピングしたものを表示(HtmlAgilityPackを使用)

概要

SgmlReaderが.NET Core対応していないので.NET Core対応しているHtmlAgilityPackを使うことにしました。 ASP.NET Coreでスクレイピングしたページを表示する簡単なWebアプリケーションを作ってみたいと思います。 おっさんにyoしてWebアプリケーションの雛形ができたところからスタートとしたいと思います。

github.com

project.jsonへの登録

dependenceisのところにこんな感じで追加。 するとVSCodeが空気を呼んでrestoreするか聞いてくれるのでやっちゃう。

"HtmlAgilityPack.NetCore": "1.5.0.1"

適当なModelを用意する

    public class Availability
    {
        public string Title {get; set;}
        public IEnumerable<DateTime> Dates {get;set;}

        public string ToString(DateTime date)
        {
            return date.ToString("yyyy年MM月dd日(dddd)");
        }
    }
    public class IndexViewModel
    {
        public List<Availability> Availabilities {get; set;}
    }

Controllerに処理を書く

SelectNodesしたときは//を入れるルールらしい。 スクレイピング自体はやっつけなのでサイトの構成変わったら動かんと思います。

        public async Task<IActionResult> Index()
        {
            // service_category
            var rootUrl = new Uri("https://as.its-kenpo.or.jp/service_category/index");
            var serviceCategoryDom = await GetHtmlAsync(rootUrl);
            var serviceGroupUrl = serviceCategoryDom.DocumentNode.SelectNodes("//a")
                .Where(x => x.InnerText == "直営・通年・夏季保養施設(空き照会)")
                .Select(x => x.Attributes["href"].Value)
                .Select(x => $"{rootUrl.Scheme}://{rootUrl.Host}" + x)
                .Select(x => new Uri(x))
                .First();

            // service_group
            var serviceGroupDom = await GetHtmlAsync(serviceGroupUrl);
            var serviceApplyUrls = serviceGroupDom.DocumentNode.SelectNodes("//li")
                .SelectMany(x => x.Descendants("a"))
                .Select(x => x.Attributes["href"].Value)
                .Select(x => $"{rootUrl.Scheme}://{rootUrl.Host}" + x)
                .Select(x => new Uri(x));
            
            // service_apply
            var availabilities = new List<Availability>();
            foreach (var serviceApplyUrl in serviceApplyUrls)
            {
                var serviceApplyDom = await GetHtmlAsync(serviceApplyUrl);
                var applyUrls = serviceApplyDom.DocumentNode.SelectNodes("//li")
                    .SelectMany(x => x.Descendants("a"))
                    .Select(x => x.Attributes["href"].Value)
                    .Select(x => $"{rootUrl.Scheme}://{rootUrl.Host}" + x)
                    .Select(x => new Uri(x));
                    
                // apply
                foreach (var applyUrl in applyUrls)
                {
                    var applyDom = await GetHtmlAsync(applyUrl);
                    var title = applyDom.DocumentNode.SelectNodes("//table")
                        .Where(x => x.Attributes["class"] != null)
                        .Where(x => x.Attributes["class"].Value == "tform_new")
                        .Select(x => x.Descendants("tr").First())
                        .Select(x => x.Descendants("td").Last().InnerText)
                        .First();
                    var dates = applyDom.DocumentNode.SelectNodes("//select")
                        .Where(x => x.Attributes["id"] != null)
                        .Where(x => x.Attributes["id"].Value == "apply_join_time")
                        .SelectMany(x => x.Descendants("option"))
                        .Select(x => x.Attributes["value"].Value)
                        .Where(x => !string.IsNullOrEmpty(x))
                        .Select(x => Convert.ToDateTime(x));

                    var availability = new Availability();
                    availability.Title = title;
                    availability.Dates = dates;
                    availabilities.Add(availability);
                }

            }

            var model = new IndexViewModel();
            model.Availabilities = availabilities;
            return View(model);
        }

Viewにモデルをバインド

@model Kenpo.Models.HomeViewModels.IndexViewModel

<ul>
@foreach(var availability in Model.Availabilities)
{
    <li>
        <span>@availability.Title</span>
        <ul>
            @foreach(var date in availability.Dates)
            {
                <li>@availability.ToString(date)</li>
            }
        </ul>
    </li>
}
</ul>

結果

勘の良い人ならわかるかもしれませんがこんな感じでIT健保の(使いにくい)サイトから空き日を一覧にしてくれるWebアプリケーションが完成します。

直営・通年・夏季保養施設 トスラブ箱根ビオーレ 2016年度9月分申込
2016年09月29日(木曜日)
直営・通年・夏季保養施設 トスラブ箱根ビオーレ 2016年度10月分申込
2016年10月24日(月曜日)
2016年10月26日(水曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 トスラブ箱根和奏林 2016年度9月分申込
2016年09月20日(火曜日)
直営・通年・夏季保養施設 トスラブ箱根和奏林 2016年度10月分申込
2016年10月06日(木曜日)
2016年10月20日(木曜日)
2016年10月24日(月曜日)
直営・通年・夏季保養施設 トスラブ湯沢 2016年度9月分申込
2016年09月19日(月曜日)
2016年09月20日(火曜日)
2016年09月22日(木曜日)
2016年09月26日(月曜日)
2016年09月28日(水曜日)
2016年09月29日(木曜日)
直営・通年・夏季保養施設 トスラブ湯沢 2016年度10月分申込
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 トスラブ館山ルアーナ 2016年度9月分申込
2016年09月29日(木曜日)
直営・通年・夏季保養施設 トスラブ館山ルアーナ 2016年度10月分申込
直営・通年・夏季保養施設 中沢ヴィレッジ 2016年度9月分申込
2016年09月29日(木曜日)
直営・通年・夏季保養施設 中沢ヴィレッジ 2016年度10月分申込
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト那須 2016年度9月分申込
2016年09月27日(火曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト那須 2016年度10月分申込
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト斑尾 2016年度9月分申込
2016年09月23日(金曜日)
2016年09月26日(月曜日)
2016年09月27日(火曜日)
2016年09月28日(水曜日)
2016年09月29日(木曜日)
2016年09月30日(金曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト斑尾 2016年度10月分申込
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月07日(金曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月16日(日曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月21日(金曜日)
2016年10月23日(日曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月30日(日曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ブルーベリーヒル勝浦 2016年度9月分申込
2016年09月19日(月曜日)
2016年09月20日(火曜日)
2016年09月21日(水曜日)
2016年09月28日(水曜日)
2016年09月29日(木曜日)
直営・通年・夏季保養施設 ブルーベリーヒル勝浦 2016年度10月分申込
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月16日(日曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月23日(日曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月30日(日曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト伊東 2016年度9月分申込
2016年09月30日(金曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト伊東 2016年度10月分申込
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト スキージャム勝山 2016年度9月分申込
2016年09月25日(日曜日)
2016年09月26日(月曜日)
2016年09月27日(火曜日)
2016年09月28日(水曜日)
2016年09月29日(木曜日)
2016年09月30日(金曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト スキージャム勝山 2016年度10月分申込
2016年10月01日(土曜日)
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月07日(金曜日)
2016年10月10日(月曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月14日(金曜日)
2016年10月15日(土曜日)
2016年10月16日(日曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月21日(金曜日)
2016年10月23日(日曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月28日(金曜日)
2016年10月30日(日曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 琵琶レイクオーツカ 2016年度9月分申込
2016年09月28日(水曜日)
直営・通年・夏季保養施設 琵琶レイクオーツカ 2016年度10月分申込
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月07日(金曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月14日(金曜日)
2016年10月16日(日曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月21日(金曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ホテル日航プリンセス京都 2016年度9月分申込
2016年09月26日(月曜日)
2016年09月27日(火曜日)
直営・通年・夏季保養施設 ホテル日航プリンセス京都 2016年度10月分申込
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト南紀田辺 2016年度9月分申込
2016年09月23日(金曜日)
2016年09月25日(日曜日)
2016年09月26日(月曜日)
2016年09月27日(火曜日)
2016年09月28日(水曜日)
2016年09月29日(木曜日)
2016年09月30日(金曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト南紀田辺 2016年度10月分申込
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月07日(金曜日)
2016年10月10日(月曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月14日(金曜日)
2016年10月16日(日曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月21日(金曜日)
2016年10月23日(日曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月28日(金曜日)
2016年10月30日(日曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 角間温泉 岩屋館 2016年度9月分申込
2016年09月26日(月曜日)
2016年09月27日(火曜日)
2016年09月30日(金曜日)
直営・通年・夏季保養施設 角間温泉 岩屋館 2016年度10月分申込
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト旧軽井沢 2016年度9月分申込
直営・通年・夏季保養施設 リゾートホテル蓼科 2016年度9月分申込
2016年09月28日(水曜日)
2016年09月29日(木曜日)
直営・通年・夏季保養施設 鎌倉パークホテル 2016年度9月分申込

ASP.NET Core でFacebook連携

基本的にはここ

Enabling authentication using Facebook, Google and other external providers — ASP.NET documentation

開発時点では環境をDevelopmentモードで起動する

本番ではUserSecretのファイルではなく環境変数を使用することになるのでDevelopmentモードで起動しなくてはいけない。やり方はふた通り

  • dotnet runのオプションとして渡す場合
dotnet run ASPNETCORE_ENVIRONMENT=Development
  • マシンの環境変数にあらかじめ設定しておく
export ASPNETCORE_ENVIRONMENT=Development
dotnet run