【React, TypeScript】Firestoreでリアルタイムアップデート
自分用のメモ
type MembersType = firebase.firestore.QueryDocumentSnapshot<firebase.firestore.DocumentData>[]; const Members = () => { const [members, setMembers] = useState<MembersType>([]); useEffect(() => { const unsubscribe = db .collection('members') .orderBy('createdAt', 'desc') .limit(10) .onSnapshot((querySnapshot) => { setMembers(querySnapshot.docs); }); return () => { unsubscribe(); }; }, []); return ( // code );
WordPressでプラグインなしで記事ごとにnoindexを設定する方法
個別記事ごとにnoindexを設定したい場合のコードです。 add_meta_box()を使ってカスタムフィールドをサイドバーに追加します。
投稿の設定の場合、wp_postmetaテーブルにデータを入れることが多いと思いますが、投稿IDの配列だけ保存できれば良いので「ブログのトップに固定(sticky_posts)」と同様にwp_optionsテーブルに保存します。
<?php class Noindex_Settings { public function __construct() { add_action('add_meta_boxes', [$this, 'custom_meta_boxes']); add_action('save_post', [$this, 'update']); } /** * カスタムフィールドのボックスを追加する */ function custom_meta_boxes() { global $post_type; add_meta_box( 'noindex_setting', '検索エンジン設定', [$this, 'insert_fields'], 'post', 'side', 'high' ); } /** * カスタムフィールドの入力エリアを設定する */ function insert_fields() { global $post; $noindex_posts = get_option('noindex_posts'); $checked = ''; if (is_array($noindex_posts)) { $noindex_posts = array_unique(array_map('intval', $noindex_posts)); if (in_array($post->ID, $noindex_posts, true)) { $checked = 'checked'; } } echo '<label> <input type="checkbox" name="noindex_posts" value="checked" ' . $checked . '> 検索エンジンに登録しない(noindex)</label>'; } /** * save_postフックでカスタムフィールドの値を保存 * @param int $post_id */ function update($post_id) { //自動保存ならアップデートしない if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } $post_id = (int) $post_id; $noindex_posts = get_option('noindex_posts'); if ($_POST['noindex_posts']) { //noindex_postsにpost_idを追加 if (!is_array($noindex_posts)) { $noindex_posts = [$post_id]; } else { $noindex_posts = array_unique( array_map('intval', $noindex_posts) ); } if (!in_array($post_id, $noindex_posts, true)) { $noindex_posts[] = $post_id; } update_option('noindex_posts', array_values($noindex_posts)); } else { //noindex_postsからpost_idを削除 if (!is_array($noindex_posts)) { return; } $noindex_posts = array_values( array_unique(array_map('intval', $noindex_posts)) ); if (!in_array($post_id, $noindex_posts, true)) { return; } $offset = array_search($post_id, $noindex_posts, true); if (false === $offset) { return; } array_splice($noindex_posts, $offset, 1); update_option('noindex_posts', $noindex_posts); } } } new Noindex_Settings();
あとはnoindexかを判定する関数を作って、metaタグでnoindex設定すればOKです。
<?php /** * noindexにするページを判定する関数 * * @return boolean */ function is_noindex() { $post_id = get_the_ID(); $noindex_posts = get_option('noindex_posts'); if (is_array($noindex_posts)) { $noindex_posts = array_map('intval', $noindex_posts); $is_noindex = in_array($post_id, $noindex_posts, true); } else { $is_noindex = false; } //アーカイブや404もnoindex判定に追加 if ( $is_noindex || is_date() || is_404() || is_search() || is_author() || is_paged() || is_attachment() ) { return true; } else { return false; } }
<?php if (is_noindex()): ?> <meta name="robots" content="noindex"> <?php endif; ?>
MySQLで集計するときに使える小技
期間を分けて集計
過去7日間とその前の7日間を比較するといった場合は、CASE文が使えます。
クエリ
SELECT CASE WHEN (DATE(m.created_at) >= DATE_ADD(CURRENT_DATE(), INTERVAL - 7 DAY)) THEN "過去7日間" ELSE "比較期間" END AS period, COUNT(DISTINCT m.id) AS count FROM members AS m WHERE DATE(m.created_at) BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 14 DAY) AND DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY) GROUP BY period
期間毎に集計する
対象期間を、7日間毎や30分毎に分けて集計したい場合は、TRANCATE()が使えます。 以下は、過去28日間の登録者数を7日間毎に集計するという例です。
クエリ
SELECT if(TRUNCATE((DATEDIFF(CURRENT_DATE(), DATE(m.created_at)) - 1) / 7, 0) >= 1, "比較期間", "過去7日間") AS period, COUNT(DISTINCT m.id) AS count FROM members AS m WHERE DATE(m.created_at) BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 28 DAY) AND DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY) GROUP BY TRUNCATE((DATEDIFF(CURRENT_DATE(), DATE(m.created_at)) - 1) / 7, 0)
(DATEDIFF(CURRENT_DATE(), DATE(m.created_at)) - 1)
で、今日とm.created_atの差を出します。
-1
しているので、差は0~13になります。
この差を7で割り、少数点以下をTRANCATE()で切り捨てることで、0~3のグループに分けることができます。
Macにrbenvを入れてからRails + Vue.jsをインストールする
rbenvでRubyのバージョン管理
$ brew update $ brew install rbenv ruby-build $ rbenv --version rbenv 1.1.1 $ rbenv install --list $ rbenv install 2.4.1 $ rbenv global 2.4.1 $ rbenv rehash $ ruby -v ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin15]
Railsを導入する
$ gem install bundler $ mkdir new_app $ cd new_app $ bundle init # gem 'rails'のコメントアウトをはずす $ vi Gemfile # オプション指定でディレクトリ配下のvendor/bundleにインストール $ bundle install --path vendor/bundle --jobs=4 # オプションを確認 $ bundle exec rails new -h # カレントディレクトリに作成。GemfileはOverwriteするか聞かれるのでY。 $ bundle exec rails new ./ -B -d mysql --skip-turbolinks --skip-test-unit --webpack=vue
サーバーを起動して、http://localhost:3000で確認。
$bundle exec rails s
Vueを入れる
Rails 5.1からはwebpackをサポートするようになったそうで、Vueを簡単に導入できます。
# Gemfileに`gem 'webpacker'`を追加 $ vi Gemfile $ bundle install # webpackとvueのインストール $ bundle exec rails webpacker:install $ bundle exec rails webpacker:install:vue
webpack
またはwebpack-dev-server
を使うことで、webpackを起動することができます。
webpack-dev-server
の場合は、以下のようなことをしてくれるそうですが、今回は試していません。
- ファイルの変更を検知して、自動リビルド&ブラウザの自動リロード
- ローカルサーバーを起動
# 以下のようなエラーがでる $ bin/webpack Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again. # エラーのコメント通りに実行 $ bundle binstubs bundler --force # 再実行 $ bin/webpack
これで最低限恩開発環境のセットは終了です。
参考: - Rails 5.1 + Vue.js で開発を行う - part1 環境構築 - Qiita - 【動画付き】Rails 5.1で作るVue.jsアプリケーション ~Herokuデプロイからシステムテストまで~ - Qiita - rails newの先輩オススメ手順 - まえとうしろ
AWS S3からほかのサーバーにファイルを一括でダウンロードする
AWS S3からファイルを一括ダウンロードするには、公式クライアントであるaws-cliが便利です。 ほかにもツールがあるようですが、今回の用途のためには公式ツール一択と考えて良いでしょう。
aws-cliをダウンロード
S3からほかのサーバーにファイルを移行する場合は、aws-cliが便利です。 まずは、移行先のサーバーにaws-cliをインストールします。
公式ドキュメントは以下の通り。
Python 2.6.5以上またはPython3.3以上が必要なので、もしインストールされていなければインストールします。
yum info python yum install -y python python-pip pip install awscli --upgrade --user
awsコマンドを使えるようにパスを通して、aws --versionで確認をします。
$ ls ~/.local/bin aws $ export PATH=~/.local/bin:$PATH $ source ~/.bash_profile $ aws --version aws-cli/1.11.84 Python/3.6.2 Linux/4.4.0-59-generic botocore/1.5.47
aws-cliの設定には、IAMユーザーのアクセスキーとシークレットアクセスキーが必要です。 IAMユーザーとは、Identity and Access Managementを省略されたもので、ユーザー毎にアクセス権限が設定されています。
初めて作成するAWS アカウントはルートユーザーと呼ばれ、AWS上のすべてのサービスに対して完全なアクセス権限を持っています。 サーバーで言うrootユーザーと同じで権限が強すぎるため、適切に権限を割り当てたIAMユーザーを作成して、これを使うことが推奨されています。
まだ、ユーザーを作成していない場合やユーザーを追加したい場合は、IAMコンソールにアクセスし「ユーザー追加」をクリックします。 作成途中の権限設定で、「AmazonS3FullAccess」を選択するようにしてください。
$ aws configure AWS Access Key ID [None]: アクセスキーを入力 AWS Secret Access Key [None]: シークレットアクセスキーを入力 Default region name [None]: 東京の場合はap-northeast-1と入力 Default output format [None]: json
設定がうまくいっているか確認するために、s3のバケットを確認します。
$ aws s3 ls 2016-10-17 18:49:32 example.com
S3からダウンロード
ファイルのダウンロードはコマンドひとつで可能です。
ディレクトリ内のファイルをすべてダウンロードする場合は、オプション--recursive
をつけます。
aws s3 cp s3://バケット名/ディレクトリ/ /保存先ディレクトリ/ --recursive
たとえば、以下のようなコマンドになります。
aws s3 cp s3://example.com/wp-content/uploads/ /var/www/html/wp-content/ --recursive
kaminariを使って外部APIのページネーションをする
外部のAPIから取得したデータをkaminariを使ってページングするときのメモです。
結論
kaminariのpaginate_arrayメソッドに、以下のように配列とtotal_countを指定することでできる。
@posts = Kaminari.paginate_array(配列, total_count: 総数) .page(現在のページ) .per(各ページ毎の表示数)
具体例
今回は、jsonを出力するAPIを用意し、そのjsonをパースしてページネーションをするということをします。
API
投稿一覧を出力するだけのシンプルなAPIを作成します。 こちらもKaminariをインストール済みとします。
コントローラー
class PostsController < ApplicationController def index @posts = Post.published.page(params[:page]).per(1) respond_to do |format| format.html { render :index } format.json { render 'index.json.jbuilder' } end end end
ビュー(jbuilder)
json.meta do # kaminariのメソッド json.total_pages @posts.total_pages json.current_page @posts.current_page json.total_count @posts.total_count json.limit_value @posts.limit_value end json.data do json.array! @posts do |post| json.id post.id json.title post.title end end
以下のようなjsonが出力されます。
{ "meta":{ "total_pages":2, "current_page":1, "total_count":2, "limit_value":1 }, "data":[ { "id":1, "title":"タイトル1", } { "id":2, "title":"タイトル2", } ] }
ページネーション
コントローラー
class ExternalPostsController < ApplicationController def index require 'net/http' require 'uri' require 'json' end_point = "https://sample.com/posts.json?page=#{params[:page]}" uri = URI.parse(end_point) response = Net::HTTP.get_response(uri) body = JSON.parse(response.body) meta = body["meta"] @posts = Kaminari.paginate_array(body["data"], total_count: meta["total_count"]) .page(meta["current_page"]) .per(meta["limit_value"]) end end
ビュー
- @posts.each do |post| # postを表示する処理 - end = paginate @posts