Quantcast
Channel: JavaScriptの記事一覧|TechRacho by BPS株式会社
Viewing all 164 articles
Browse latest View live

Vue.jsサンプルコード(27)選択肢に応じて入力項目を切り替える

$
0
0

27. 選択肢に応じて入力項目を切り替える

  • Vue.jsバージョン: 2.5.2
  • 上部の3つのラジオボタンをクリックすると、下の入力方法が変更されます。
    • 「クレジットカード」: [番号]と[年月日]フィールドが表示されます。年月日はDatePickerでも入力できます。
    • 「銀行振込」: [口座]フィールドが表示されます。
    • 「チケット」: [コード]フィールドが表示されます。
  • 画面をリロードすると最初の状態に戻ります。

サンプルコード


バックナンバー(Vue.jsサンプルコード)

Vue.jsサンプルコード(01〜03)Hello World・簡単な導入方法・デバッグ・結果の表示とメモ化


JavaScript: Parcel.jsでReact.jsプロジェクトを作成する(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

Parcel.jsでReact.jsプロジェクトを作成する(翻訳)

Parcel.jsはJavaScript界隈に登場した新しいbundlerです。誕生後2週間も経たないうちにGitHubで★11,000以上も獲得しています。本記事はParcel.jsで最初のReactプロジェクトを作成するためのガイドです。

Parcel.jsは、(マルチコア処理で驚異的な高速性が実現されるまでは)JavaScript bundlerとして最速であることと「設定ゼロ」を誇っています。詳しくはParceljs.orgをご覧ください。


  • 1. Parcel.jsは、インストールするだけですぐ使えるようになります。
npm install -g parcel-bundler
  • 2. プロジェクトディレクトリで以下のコマンドを実行し、package.jsonファイルを作成します。
npm init -y
  • 3. ReactとReact-domをインストールします。
npm install --save react react-dom
  • 4. 開発用の依存ライブラリをインストールします。
npm install --save-dev parcel-bundler babel-preset-env babel-preset-react
  • 5. Reactのプリセットを使うための.babelrcを作成します。
// .babelrc

{  "presets": ["env", "react"]}
  • 6. package.jsonに起動スクリプトを追加します。
// package.json

"scripts": {  "start": "parcel index.html"}
  • 7. index.htmlを作成し、index.jsを指すscriptタグを追加します(Gist)。
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>React-Parcel-Example</title>
  </head>

  <body>
    <div id="root"></div>
  </body>

  <script src="./index.js"></script>

</html>
  • 8. index.jsを作成し、コンポーネントのimportとページのレンダリングを行えるようにします(Gist)。
// index.js
import React from 'react';
import ReactDOM from 'react-dom';

import ExampleApp from './ExampleApp';

ReactDOM.render(<ExampleApp />, document.getElementById('root'));
  • 9. コンポーネントファイルとCSSファイルを作成します。
    • ExampleApp.jsは画像とタイトルを持つコンポーネントを単に返します(Gist)。
// ExampleApp.js
import React, { Component } from 'react';

import './ExampleApp.css';

class ExampleApp extends Component {
  render() {
    return (
      <div className="App">
        <img
          className="App-Logo"
          src="https://user-images.githubusercontent.com/19409/31321658-f6aed0f2-ac3d-11e7-8100-1587e676e0ec.png"
          alt="Parcel Logo"
        />
        <h1 className="App-Title">React-Parcel Example</h1>
      </div>
    );
  }
}

export default ExampleApp;
  • ExampleApp.css(Gist
/* ExampleApp.css */

body {
  margin: 0;
  padding: 0;
  font-family: Roboto, 'Helvetica Neue', Arial, sans-serif;
}

.App {
  text-align: center;
  margin: 2rem auto;
}

img {
  height: 100px;
}
  • 10. 以上でおしまいです。Parcelのdevサーバーを起動すると変更が即座に反映されるようになります。
npm run start

ここでご紹介したコードはGitHubリポジトリでご覧いただけます。また、実際にサンプルアプリを見ることもできます。


お読みいただきありがとうございました。JavaScriptとPythonの開発者およびメンターのVignesh Mです。ユーザー名は@vigzmvで通しています。もっと読みたい方はフォローをお願いします。

関連記事

JavaScript: 5分でわかるPromiseの基礎(翻訳)

JavaScriptスタイルガイド 1〜8: 型、参照、オブジェクト、配列、関数ほか (翻訳)

新しいRailsフロントエンド開発(2)コンポーネントベースでアプリを書く(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

新しいRailsフロントエンド開発(2)コンポーネントベースでアプリを書く(翻訳)

前書き

本記事は、フロントエンドのフレームワークに依存しないRailsプレゼンテーションロジックを現代的かつモジュール単位かつコンポーネントベースで扱う方法を独断に基いて解説するガイドです。3部構成のチュートリアルで、例を元に最新のフロントエンド技術の最小限に学習し、Railsフロントエンド周りをすべて理解しましょう。

Part 1のおさらい

こちらもお読みください: Part 1

Hacker NewsReddit数々の議論を呼び起こしたPart 1では、標準的なRailsアプリを現代的なフロントエンドプラクティスに合わせて組み替えました。Webpacker gemを用いてアセットをWebpackでビルドしつつ、CSSをPostCSScssnextで処理しています。BabelAutoprefixerBrowserslistのおかげでクロスブラウザの問題に悩まされずに済むようになりました。git commitのたびにPrettierAirBnB Base ConfigESLintstylelintでコードの文法エラーを自動チェックできるようになりました。

フォルダ構造をわかりやすく変えてコンポーネント指向で考えられるようにし、それでいてReactなどのいかなるフロントエンドフレームワークにも依存しません。昔ながらの.erbパーシャルもこれまでどおり扱えます。開発中はいつものrails sの代わりに弊社が推しているhivemindこちらからどうぞ)やforemanでサーバーを起動します。

チュートリアルPart 2のコードを含むGitHubリポジトリでコードをすぐにご覧いただけます。

ここまでのアプリは「Hello World」メッセージを表示する機能しかなく、まだ体をなしていません。今回は現実のアプリを作りましょう。チュートリアルに沿って私たちと一緒にアプリを作成するときはかなりコピペを繰り返すことになります(もちろんコード例を手入力しても好みに応じて変更しても構いません)。まずはPart 1を完了させておきましょう。

アプリを現実に近づける

前回表示に使った以下のコンポーネントを思い出しましょう。

<!-- app/views/pages/home.html.erb -->
<%= render "components/page/page" do %>
  <p>Hello from our first component!</p>
<% end %>

コンポーネントをレンダリングするヘルパーを導入して少々楽をしましょう。次のような感じです。

<%= c("page") do %>
  <%= c("auth-form") %>
<% end %>

これでフルパスを入力しなくてもコンポーネント名だけを指定するだけで済むようになります。このヘルパーは、同じフォルダ内に置かれた、機能がほんの少し異なる2つのパーシャルを扱うこともできます(_message-form.html.erb_message-form_admin.html.erbなど)。2つのパーシャルを区別しやすくするため、アンダースコア_を慣習として使っています。

application_helper.rbを開いてメソッドを1つ追加します。

module ApplicationHelper
  def component(component_name, locals = {}, &block)
    name = component_name.split("_").first
    render("components/#{name}/#{component_name}", locals, &block)
  end

  alias :c :component
end

次はコントローラです。現時点ではスモークテストで必要だったpages_controller.rbが1つあるだけです。これは削除しても問題ありません(その場合、対応するapp/views/pagesフォルダも削除します)。私たちのチャットアプリには、認証用のAuthControllerと、チャットウィンドウを受け持つChatControllerの2つのコントローラを置くことにします。次のコマンドで2つのコントローラを生成できます。

$ rails g controller auth
$ rails g controller chat

routes.rbも変更しておきます。

Rails.application.routes.draw do
  root to: "chat#show"

  get  "/login", to: "auth#new"
  post "/login", to: "auth#create"
end

認証ページの作成に取り掛かりましょう。

# app/controllers/auth_controller.rb
class AuthController < ApplicationController
  before_action :only_for_anonymous # 既知のユーザーかどうかをチェック

  def new; end

  # paramsからusernameを取得し、sessionに保存してチャットにリダイレクトする
  def create
    session[:username] = params[:username]
    redirect_to root_path
  end

  private

  # ユーザーが以前チャットしたことがある場合はそのままチャットウィンドウにリダイレクト
  def only_for_anonymous
    redirect_to root_path if session[:username]
  end
end

サンプルアプリなのでアクションはかなりシンプルです。初めてのユーザーにはusernameの入力を求め、それをsessionハッシュに保存します。リピーターの場合は認証ページをスキップします。newアクションで必要なビューは1つだけなので、作成してみましょう。設計上、ビューテンプレートにはコンポーネントのパーシャルを呼び出すrender呼び出しのみを含めるべきです。ここでは、Part 1の最後に作成したpageコンポーネントの内部にauthコンポーネントを埋め込みます。

$ touch app/views/auth/new.html.erb
<!-- app/views/auth/new.html.erb -->
<%= c("page") do %>
  <%= c("auth-form") %>
<% end %>

今度は認証フォーム用のコンポーネントを1つ作成しましょう。これには明示的にauth-formという名前を付けます。

$ mkdir -p frontend/components/auth-form
$ touch frontend/components/auth-form/{auth-form.css,auth-form.js,_auth-form.html.erb}

手作業が面倒になってきた方は、本記事の末尾にあるコンポーネント用ジェネレータの導入部分までスキップしてください。

新しいコンポーネントを1つ作成するたびに、これらの2つのコマンドを実行します。手始めに.erbパーシャルからやってみましょう。ここでは標準的なRailsヘルパーを使って標準的なフォームを作ります。

<!-- frontend/components/auth-form/_auth-form.html.erb -->
<div class="auth-form">
  <%= form_tag login_path, method: :post do %>
   <%= text_field_tag :username, "", class: "auth-form--input", placeholder: "Choose your username...", autofocus: true, required: true %>
   <%= submit_tag "Identify me", class: "auth-form--submit" %>
  <% end %>
</div>

最初の時点でCSSの命名ルールを定めておくのも合理的です。

明快な命名法を選ぶことで、共通の名前空間で名前の衝突を避けられますし、コードが自ら語るようになります。


本記事でご覧いただいているアプローチではCSS Modules使っていませんので、名前が衝突しないよう辛抱強くCSSに名前を付けることにします。

参考: CSS Modules所感

「ブロック/要素」アプローチを採用したいので、BEMのハンドブックから拝借することにします(ブロックは私たちのコンポーネント、要素はその論理的なパーツに相当します)。BEMの書式component-name--element-nameを選択します。こうすることで、テキストフィールドや送信ボタンは次のクラスに従う必要があります。auth-form--inputauth-formがコンポーネント、inputが要素になります。auth-form--submitauth-formがコンポーネント、submitが要素になります。BEMの「M」はmodifierの略ですが、このアプリでは簡単のためmodifierは使わないことにします。

もちろん、CSS命名ルールは、コンポーネント間で統一されていれば、各自のこだわりに合わせていただいて構いません。

とりあえずスタイルの下地はできあがりましたが、まだ何も追加されていません。現時点の認証ページ(localhost:5000/login)は次のようになっています。

スタイルなしの認証ページ

スタイルなしの認証ページ

ここで一手間かけて、CSSクラスをネストできるpostcss-nestedプラグインも有効にしておきましょう。ターミナルでyarn add postcss-nestedと入力し、pluginsセクション内の冒頭行に.postcssrc.yml: postcss-nested: {}を追記します。

それではいよいよスタイルをいくつか足してみましょう。スタイルはWebpackからJavaScript経由で取り込まれるので、常にコンポーネントのスタイルシートをimportでコンポーネントのJavaScriptファイルに取り込む必要があります。また、application.jsエントリポイントの内部でコンポーネントを「登録」する必要もあります。

// frontend/packs/application.js
import "init";
import "components/page/page";
import "components/auth-form/auth-form";
// frontend/components/auth-form/auth-form.js
import "./auth-form.css";
/* frontend/components/auth-form/auth-form.css */
.auth-form {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;

  &--input {
    width: 100%;
    padding: 12px 0;
    border: 1px solid rgba(0, 0, 0, 0.1);
    font-size: 18px;
    text-align: center;
    outline: none;
    transition: border-color 150ms;
    box-sizing: border-box;

    &:hover,
    &:focus {
      border: 1px solid #3f94f9;
    }
  }

  &--submit {
    width: 100%;
    margin-top: 6px;
    padding: 12px 0;
    background: #3f94f9;
    border: 1px solid #3f94f9;
    color: white;
    font-size: 18px;
    outline: none;
    transition: opacity 150ms;
    cursor: pointer;

    &:hover,
    &:focus {
      opacity: 0.7;
    }
  }
}

component-name--element-nameという命名ルールのおかげで、ネストした PostCSSをアンパサンド&で簡単に書けるようになったのがわかります。この&は、PostCSSが純粋なCSSに変換されるときに単に「親」クラス名に置き換えられるので、.auth-form { &--input }.auth-form.auth-form--inputの2つの別々のクラスになります。しかし私たちのコードでは、auth-formコンポーネントに関連するものはすべてauth-formクラスのスコープ内に含まれるので、クラス名の衝突を気にする必要はありません。ポイントは、「親」CSSクラス名をプロジェクト内のコンポーネントとそのフォルダに正確に一致させることです。こうしないと、たちまちコードがスパゲッティになってしまうでしょう。

これで、(サーバーが既に動いていれば)ブラウザウィンドウに戻るとログインページにスタイルが追加されていることがわかります。webpack-dev-serverはJavaScriptファイルの変更を検出してバックグラウンドでページを更新します。

スタイル付きの認証ページ

スタイル付きの認証ページ

CSSをこんなに簡単にいじれるようになったのがおわかりでしょうか?ボタンの色を変える必要があるなら、ブラウザとコードエディタをそれぞれ開いて横に並べて作業すれば、変更したファイルを保存するたびにブラウザに即座に反映されます。これでスタイル変更作業が非常にはかどります。

: このフォームを送信して認証ページが表示されなくなった場合(コントローラの現在のロジックでは、ユーザー名がsessionに保存されると戻れなくなります)、ブラウザのcookieを削除してください。

メッセンジャーを撃たないで

訳注: Don’t shoot the messengerはYouTubeのコメディ番組のタイトルで、shooting the messenger(悪い知らせをもたらした人を責める言い回し)のもじりです。Pusciferのアルバムタイトル「Don’t shoot the messenger」でもあります。

認証ページからどこか別のページにユーザーを導く必要がありますが、現時点ではルーティングが少々とからっぽのChatControllerしかありません。メッセージを扱えるようにしたいので、基本的なMessageモデルが必要です。さっそく作ってみましょう。

$ rails g model message author:string text:text
$ rails db:create
$ rails db:migrate

メッセージはActionCableを使って作成されるので、メッセージを表示する何らかの方法がコントローラに必要です。ページを最初に読み込んだときに最新の20件を表示することにします。

# app/controllers/chat_controller.rb
class ChatController < ApplicationController
  before_action :authenticate!

  # 最新メッセージを20件表示
  def show
    @messages = Message.order(created_at: :asc).last(20)
  end

  private

  # ユーザーがusernameを指定しなかった場合/loginにリダイレクト
  def authenticate!
    redirect_to login_path unless session[:username]
  end
end

繰り返しますが、ビューは1つあれば十分です。今回はshow.html.erbを作成します。

$ touch app/views/chat/show.html.erb
<!-- app/views/chat/show.html.erb -->
<%= c("page") do %>
  <%= c("chat", messages: @messages) %>
<% end %>

コンポーネントは単なる純粋なERBパーシャルであり、renderメソッドを使うヘルパーによってレンダリングされるので、いつもと同じようにローカルを渡します。コンポーネントの追加方法は既に学びましたね。

$ mkdir -p frontend/components/chat
$ touch frontend/components/chat/{chat.css,chat.js,_chat.html.erb}

ここからコンポーネントのネストが深くなります。私たちのchatコンポーネントは、ページのコンテンツ全体を参照する方法の1つです。ページには、動的に更新されるメッセージリストと、新しいメッセージを送信するフォームを1つずつ作成するので、messagesmessage-formの2つのコンポーネントに分割できます。また、メッセージが複数あるところにはメッセージが1件あるので、messageコンポーネントも必要です。ターミナルでもう少し作業しましょう。

$ mkdir -p frontend/components/message
$ touch frontend/components/message/{message.css,message.js,_message.html.erb}

$ mkdir -p frontend/components/messages
$ touch frontend/components/messages/{messages.css,messages.js,_messages.html.erb}

$ mkdir -p frontend/components/message-form
$ touch frontend/components/message-form/{message-form.css,message-form.js,_message-form.html.erb}

ファイルとフォルダの作成がすべて終わると、次のような構造になるはずです。

frontend/components
   ├── auth-form
   │   ├── _auth-form.html.erb
   │   ├── auth-form.css
   │   └── auth-form.js
   ├── chat
   │   ├── _chat.html.erb
   │   ├── chat.css
   │   └── chat.js
   ├── message
   │   ├── _message.html.erb
   │   ├── message.css
   │   └── message.js
   ├── message-form
   │   ├── _message-form.html.erb
   │   ├── message-form.css
   │   └── message-form.js
   ├── messages
   │   ├── _messages.html.erb
   │   ├── messages.css
   │   └── messages.js
   └── page
       ├── _page.html.erb
       ├── page.css
       └── page.js

親コンポーネントchatでコードの空白を埋めていきます。

<!-- frontend/components/chat/_chat.html.erb -->
<div class="chat">
 <div class="chat--messages">
   <%= c("messages", messages: messages) %>
 </div>
 <div class="chat--form">
   <%= c("message-form") %>
 </div>
</div>

上のコードから、このコンポーネントはサブコンポーネントもレンダリングすることがわかりますが、サブコンポーネントを個別のエントリポイントにすべて入れたくないので、このままではすぐ手に負えなくなってしまう可能性があります。そこで次の経験則を導入することにします。「あるコンポーネントに子が1つ以上ある場合は、子をcomponent’s .jsファイルでimportすること」。こうすることで、application.jsには階層のトップに位置するコンポーネントだけを登録すれば済むようになります。ここで正しい方法でやっておけば、後々忘れずに済みます。

// 更新後のfrontend/packs/application.js
import "init";
import "components/page/page";
import "components/auth-form/auth-form";
import "components/chat/chat";

続いて、chat内部のネストしたコンポーネントのJSファイルをchat.jsでインポートします。

// frontend/components/chat/chat.js
import "components/messages/messages";
import "components/message-form/message-form";
import "./chat.css";

最後はCSSです。

/* frontend/components/chat/chat.css */
.chat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 100%;
  height: 100%;
  overflow: hidden;

  &--messages {
    width: 100%;
    flex: 1 0 0;
  }

  &--form {
    width: 100%;
    background: white;
    flex: 0 0 50px;
  }
}

1つ目のコンポーネントが終わりました。あと3つです!

message-formのERBは次のとおりです。

<!-- frontend/components/message-form/_message-form.html.erb -->
<div class="message-form js-message-form">
  <textarea class="message-form--input js-message-form--input" autofocus></textarea>
  <button class="message-form--submit js-message-form--submit">Send</button>
</div>

ここでは<form>タグを使っていないことにご注意ください。ActionCableを使うために、<textarea>の内容をJavaScriptで送信するからです。

おそらく、ここでクラス名がmessage-formjs-message-formと2回使われている点が気になる方がいらっしゃると思います。この慣習に従っておくことで、設計が変更されてクラス名が変更されたときに、JavaScriptのセレクタが影響を受けずに済みます。つまり、CSSの名前とJavaScriptの名前の2とおりの命名が共存することになります。皆さんのコードでこの通りにする必要はありませんので、単一のセレクタを使ってもかまいません。しかしその場合、CSSクラス名が変更されるたびに、再設計でロジックが壊れないようにするためにDOMを操作するJavaScriptコードも手動で変更しなければならなくなります。

// frontend/components/message-form/message-form.js
import "./message-form.css";
/* frontend/components/message-form/message-form.css */
.message-form {
  display: flex;
  width: 100%;
  height: 100%;

  &--input {
    flex: 1 1 auto;
    padding: 12px;
    border: 1px solid rgba(0, 0, 0, 0.1);
    font-size: 18px;
    outline: none;
    transition: border-color 150ms;
    box-sizing: border-box;
    resize: none;

    &:hover,
    &:focus {
      border: 1px solid #3f94f9;
    }
  }

  &--submit {
    flex: 0 1 auto;
    height: 100%;
    padding: 12px 48px;
    background: #3f94f9;
    border: 1px solid #3f94f9;
    color: white;
    font-size: 18px;
    outline: none;
    transition: opacity 150ms;
    cursor: pointer;

    &:hover,
    &:focus {
      opacity: 0.7;
    }

    &:active {
      transform: translateY(2px);
    }
  }
}

作業中はいつでもlocalhost:5000でチャットウィンドウを表示できます。準備ができていないコンポーネントについてはcレンダリング呼び出しをコメントアウトして止めておくことだけお忘れなく。

先に進みましょう。ここまでで、親コンポーネントとフォームが1つずつできました。次は、メッセージを表示する場所と、各メッセージのテンプレートが必要です。これまでのパターンどおり、ERB、JS、CSSの順に作成します。

<!-- frontend/components/messages/_messages.html.erb -->
<div class="messages js-messages">
  <div class="messages--content js-messages--content">
    <% messages.each do |message| %>
      <%= c("message", message: message) %>
    <% end %>
  </div>
</div>
// frontend/components/messages/messages.js
import "components/message/message"; // メッセージはネストされるので、ここでimportする
import "./messages.css";
/* frontend/components/messages/messages.css */
.messages {
  position: relative;
  width: 100%;
  height: 100%;
  background: white;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-bottom: 0;
  box-sizing: border-box;

  &--content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    overflow-x: hidden;
    overflow-y: auto;
  }
}

最後は個別のメッセージのコードです。

<!-- frontend/components/message/_message.html.erb -->
<div class="message">
  <div class="message--header">
    <span class="message--author">
      <%= message.author %>
    </span>
    <span class="message--time">
      <% if message.created_at > Time.now - 24.hours %>
        <%= l(message.created_at, format: :short) %>
      <% else %>
        <%= l(message.created_at, format: :long) %>
      <% end %>
    </span>
  </div>
  <div class="message--text">
    <% message.text.lines.each do |line| %>
      <p><%= line %></p>
    <% end %>
  </div>
</div>
// frontend/components/message/message.js
import "./message.css";
/* frontend/components/message/message.css */
.message {
  margin: 12px 6px;

  &:first-child {
    margin-top: 0;
  }

  &:last-child {
    margin-bottom: 0;
  }

  &--author {
    font-weight: bold;
  }

  &--time {
    color: rgba(0, 0, 0, 0.5);
    font-size: 12px;
  }

  &--text p {
    margin: 0;
  }
}

ここまでの作業がすべてうまくいっているかどうかテストしましょう。まだフォームでメッセージを作成できないので、rails consoleMessageインスタンスをいくつか作成し、正しく表示されるかどうかを実際にチェックします。

# rails consoleで以下を入力する
> Message.create(author: "Evil Martian", text: "Surrender!")

サーバーが実行されていることを確認し、ブラウザを更新します。上のとおりに進めていれば、以下のように表示されるはずです。

チャットウィンドウ

チャットウィンドウ

おまけ

コンポーネントのフォルダやファイルの手動作成ばかり続いて疲れたら、ここでご紹介するRailsジェネレータを使って必要に応じて調整するとよいでしょう。libフォルダの中にgeneratorというフォルダを作成し、そこにcomponent_generator.rbというファイルを置いて以下を記述します。

$ mkdir lib/generators
$ touch lib/generators/component_generator.rb
# lib/generators/component_generator.rb
class ComponentGenerator < Rails::Generators::Base
  argument :component_name, required: true, desc: "Component name, e.g: button"

  def create_view_file
    create_file "#{component_path}/_#{component_name}.html.erb"
  end

  def create_css_file
    create_file "#{component_path}/#{component_name}.css"
  end

  def create_js_file
    create_file "#{component_path}/#{component_name}.js" do
      # コンポーネントのCSSをJS内で自動requireする
      "import \"./#{component_name}.css\";\n"
    end
  end

  protected

  def component_path
    "frontend/components/#{component_name}"
  end
end

これで以下のコマンドラインでコンポーネントを生成できます。

$ rails g component コンポーネント名

チュートリアルPart 2の完了おめでとうございます!もしうまく動かない場合はGitHubリポジトリのコードでチェックしましょう。ここまでお読みいただきありがとうございます。次回Part 3では、いよいよActionCableでアプリをインタラクティブにし、いくつか仕上げ作業を行ってからHerokuにデプロイします。「sprockets抜き」のRailsアプリで生じる問題についても取り上げます。どうぞお楽しみに!


Part 1 | Part 2 | Part 3

スタートアップをワープ速度で成長させられる地球外エンジニアよ!Evil Martiansのフォームにて待つ。

関連記事

新しいRailsフロントエンド開発(1)Asset PipelineからWebpackへ(翻訳)

Vue.jsサンプルコード(28)文字の大きさをボタンで変更する

$
0
0

28. 文字の大きさをボタンで変更する

  • Vue.jsバージョン: 2.5.2
  • [小]ボタンと[大]ボタンをクリックすると、「文字の大きさ」という文字列のフォントサイズが変更されます。
  • 画面をリロードすると最初の状態に戻ります。

サンプルコード


バックナンバー(Vue.jsサンプルコード)

Vue.jsサンプルコード(01〜03)Hello World・簡単な導入方法・デバッグ・結果の表示とメモ化

週刊Railsウォッチ(20180112)update_attributeが修正、ぼっち演算子`&.`は`Object#try`より高速、今年のRubyカンファレンス情報ほか

$
0
0

こんにちは、hachi8833です。インフルエンザA型が身に沁みました。

2018年最初のウォッチ、いってみましょう。年末年始を挟んでだいぶ記事がたまっているのでいつもより多めです。

Rails: 今週の改修

Ruby 2.5をCIに追加

まずは縁起物コミットから。

# travis.yml
   - 2.2.8
   - 2.3.5
   - 2.4.2
+  - 2.5.0
   - ruby-head

 matrix:
   include:
-    - rvm: 2.4.2
+    - rvm: 2.5.0

PostgreSQLでbulk_change_tableをサポート

MySQLでは以前からbulk: trueが使えるそうです。

# activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#367
+        def bulk_change_table(table_name, operations)
+          sql_fragments = []
+          non_combinable_operations = []
+
+          operations.each do |command, args|
+            table, arguments = args.shift, args
+            method = :"#{command}_for_alter"
+
+            if respond_to?(method, true)
+              sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
+              sql_fragments << sqls
+              non_combinable_operations << procs if procs.present?
+            else
+              execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
+              non_combinable_operations.each(&:call)
+              sql_fragments = []
+              non_combinable_operations = []
+              send(command, table, *arguments)
+            end
+          end
+
+          execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
+          non_combinable_operations.each(&:call)
+        end

MiniMagickでcombine_optionsをサポート

# activestorage/app/models/active_storage/variation.rb#48
   def transform(image)
-    transformations.each do |method, argument|
-      image.mogrify do |command|
-        if eligible_argument?(argument)
-          command.public_send(method, argument)
-        else
-          command.public_send(method)
+    transformations.each do |(method, argument)|
+      if method.to_s == "combine_options"
+        image.combine_options do |combination|
+          argument.each do |(method, argument)|
+            pass_transform_argument(combination, method, argument)
+          end
         end
+      else
+        pass_transform_argument(image, method, argument)
       end
     end
   end

つっつきボイス: 「MiniMagickが好きと聞いて」「好きというほどではw: ImageMagickに比べればマシかなぐらい」

[Rails] MiniMagickでPDFのページ数を取得するときはフォントエラーに注意!

PostgreSQLのrange typeでFloat::INFINITYをサポート

rangeが空文字列にならないようFloat::INFINITYに型変換するようになりました。

# activerecord/test/cases/adapters/postgresql/range_test.rb#361
+    def test_infinity_values
+      PostgresqlRange.create!(int4_range: 1..Float::INFINITY,
+                              int8_range: -Float::INFINITY..0,
+                              float_range: -Float::INFINITY..Float::INFINITY)
+
+      record = PostgresqlRange.first
+
+      assert_equal(1...Float::INFINITY, record.int4_range)
+      assert_equal(-Float::INFINITY...1, record.int8_range)
+      assert_equal(-Float::INFINITY...Float::INFINITY, record.float_range)
+    end

つっつきボイス: 「PostgreSQLのrangeって無限が使えるのか」「無限こわい」

参考: PG10マニュアル: 8.17.4. Infinite (Unbounded) Ranges

()[]を使い分けるんですね。Lintに怒られそう。

(lower-bound,upper-bound)
(lower-bound,upper-bound]
[lower-bound,upper-bound)
[lower-bound,upper-bound]
empty

逆関連付けで外部キーが更新されていなかったのを修正

# activerecord/test/cases/associations/has_many_associations_test.rb#2512
+  test "reattach to new objects replaces inverse association and foreign key" do
+    bulb = Bulb.create!(car: Car.create!)
+    assert bulb.car_id
+    car = Car.new
+    car.bulbs << bulb
+    assert_equal car, bulb.car
+    assert_nil bulb.car_id
+  end

つっつきボイス: 「inverse association、この間案件に出てきたナ」「逆関連付け、でいいのかな」

validationコールバックが複数コンテキストで発火しなくなったのを修正

#21069で実装されていたのがいつの間にか動かなくなっていたので修正されたそうです。

class Dog
  include ActiveModel::Validations
  include ActiveModel::Validations::Callbacks

  attr_accessor :history

  def initialize
    @history = []
  end

  before_validation :set_before_validation_on_a, on: :a
  before_validation :set_before_validation_on_b, on: :b
  after_validation :set_after_validation_on_a, on: :a
  after_validation :set_after_validation_on_b, on: :b

  def set_before_validation_on_a; history << "before_validation on a"; end
  def set_before_validation_on_b; history << "before_validation on b"; end
  def set_after_validation_on_a;  history << "after_validation on a" ; end
  def set_after_validation_on_b;  history << "after_validation on b" ; end
end
d = Dog.new
d.valid?([:a, :b])
# 修正前
d.history #=> []
# 修正後
d.history #=> ["before_validation on a", "before_validation on b", "after_validation on a", "after_validation on b"]

つっつきボイス: 「やや、before/afterコールバックのon:オプションって初めて知ったけどこれは?」「on:はコンテキストを限定するのに使うやつですね: その条件が満たされるときだけコールバックされる」「なるほど~: if書きたくないマンにはうれしい機能」「条件が複雑になったらifで書かないと見落とすかもですね」

ActiveStorageで扱う添付ファイルの拡張子を追加

# activestorage/lib/active_storage/engine.rb
+    config.active_storage.content_types_to_serve_as_binary = [
+      "text/html",
+      "text/javascript",
+      "image/svg+xml",
+      "application/postscript",
+      "application/x-shockwave-flash",
+      "text/xml",
+      "application/xml",
+      "application/xhtml+xml"
+    ]

つっつきボイス: 「content dispositionって何でしたっけ」「ファイルをインラインで表示するかダウンロードダイアログを出すかの扱いっすね」「まさにコミットメッセージに書いてあった」

String.blank?のエンコーディングがUTF-16LEでエラーになるのを修正

ActiveSupportでStringクラスを開いて修正しています。

# activesupport/lib/active_support/core_ext/object/blank.rb#104
class String
   BLANK_RE = /\A[[:space:]]*\z/
+  ENCODED_BLANKS = Concurrent::Map.new do |h, enc|
+    h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING)
+  end

つっつきボイス: 「UTF-16ってASCII互換じゃないしエンディアンとかBOMとかサロゲートペアとかいろいろ残念で残念で: これを標準にしちゃったWindowsって(略」「出たな文字コード厨w」

参考: Wikipedia-ja: UTF-16

属性が見つからない場合の挙動を修正

# activerecord/lib/active_record/attribute.rb#234
+        def forgetting_assignment
+          dup
+        end

つっつきボイス: 「dirty save周りの修正っすね」

#25503のupdate_attributeの挙動がついに修正

昨年末のRailsウォッチで言及した#25503 update_attribute ignores autosave relationsが2年越しでついに修正されました。

# activerecord/lib/active_record/persistence.rb#405
-      if has_changes_to_save?
-        save(validate: false)
-      else
-        true
-      end
+      save(validate: false)
      end

つっつきボイス: 「例のGobyちゃんの作者のst0012さんが、このバグが直ってないって昨年落ち込んでました」「おお!これが修正されたということは、例のQiitaの定番記事『ActiveRecord の attribute 更新方法まとめ』のupdate_attributeの記述↓も修正してもらわないと」


ActiveRecord の attribute 更新方法まとめより


Goby: Rubyライクな言語(1)Gobyを動かしてみる

Rails

Rails.application.routes.url_helpersを直接呼ぶと遅い

# 直接呼んだ場合
Requests per second:    55.08 [#/sec] (mean)
Time per request:       18.155 [ms] (mean)
# モジュールをクラスにincludeした場合
Requests per second:    117.09 [#/sec] (mean)
Time per request:       8.540 [ms] (mean)

issue #23451 Performance Regression using url_routerとそれを修正するPR#24554 Memoize the RouteSet#url_helpers moduleが前から上がっていますがまだmergeされていません。それまではinclude Rails.application.routes.url_helpersする方が速いそうです。

# app/whatever/url_helper.rb
class UrlHelper
  include Singleton
  include Rails.application.routes.url_helpers
end

Rails 5.2でMySQLの降順インデックスをサポート(RubyFlowより)

# 同記事より
create_table "reports", force: :cascade do |t|
  t.string "name"
  t.integer "user_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["user_id", "name"], name: "index_reports_on_user_id_and_name", order: { name: :desc }
end

Railsのsystem testをRSpecから使う

# 同記事より
require 'rails_helper'

describe 'Homepage' do
  before do
    driven_by :selenium_chrome_headless
  end

  it 'shows greeting' do
    visit root_url
    expect(page).to have_content 'Hello World'
  end
end

つっつきボイス: 「この間takanekoさんに教えてもらった@jnchitoさんの記事↓の方がたいてい詳しいんですが、一応」

whatson: 今後開催予定のRubyカンファレンスを一覧表示するgem

もともとRuby Conferences (& Camps) in 2018 – What’s Upcoming?をご紹介しようと思っていたのですが、リンク先がアドベントカレンダーのせいか今日になって消滅していて、その情報源であるこのgemを見つけました。
私はうれしいですがほぼ誰得なgemですね。

$ gem install whatson
$ rubyconf
Upcoming Ruby Conferences:

  in 14d  Ruby on Ice Conference, Fri-Sun Jan/26-28 (3d) @ Tegernsee, Bavaria (near Munich / München) › Germany / Deutschland (de) › Central Europe › Europe
  in 20d  RubyFuza, Thu-Sat Feb/1-3 (3d) @ Cape Town › South Africa (za) › Africa
  in 28d  RubyConf India, Fri+Sat Feb/9+10 (2d) @ Bengaluru › India (in) › Asia
  in 55d  RubyConf Australia, Thu+Fri Mar/8+9 (2d) @ Sydney › Australia (au) › Pacific / Oceania
  in 62d  RubyConf Philippines, Thu-Sat Mar/15-17 (3d) @ Manila › Philippines / Pilipinas (ph) › Asia
  in 63d  wroc_love.rb, Fri-Sun Mar/16-18 (3d) @ Wrocław › Poland (pl) › Central Europe › Europe
  in 69d  Bath Ruby Conference, Thu+Fri Mar/22+23 (2d) @ Bath, Somerset › England (en) › Western Europe › Europe
  in 95d  RailsConf, Tue-Thu Apr/17-19 (3d) @ Pittsburgh, Pennsylvania › United States (us) › North America › America
  in 105d  RubyConf Taiwan, Fri+Sat Apr/27+28 (2d) @ Taipei › Taiwan (tw) › Asia
  in 111d  Rubyhack: High Altitude Coding Konference, Thu+Fri May/3+4 (2d) @ Salt Lake City, Utah › Southwest › United States (us) › North America › America
  in 133d  Balkan Ruby Conference, Fri+Sat May/25+26 (2d) @ Sofia › Bulgaria (bg) › Eastern Europe › Europe
  in 139d  RubyKaigi, Thu-Sat May/31-Jun/2 (3d) @ Sendai › Japan (jp) › Asia
  in 161d  RubyConf Kenya, Fri Jun/22 (1d) @ Nairobi › Kenya (ke) › Africa
  in 167d  Paris.rb XXL Conf, Thu+Fri Jun/28+29 (2d) @ Paris › France (fr) › Western Europe › Europe
  in 175d  Brighton Ruby Conference, Fri Jul/6 (1d) @ Brighton, East Sussex › England (en) › Western Europe › Europe
  in 305d  RubyConf, Tue-Thu Nov/13-15 (3d) @ Los Angeles, California › United States (us) › North America › America

    More @ github.com/planetruby/awesome-events

ついでに、元サイトがhttp://planetruby.herokuapp.com/というRuby情報クローラ的なサイトになっていました。

今年のリストでは、アフリカ大陸(南アフリカ共和国とケニア)でRubyカンファレンスが開催されるのが目につきました。ケニアのはその名もrubyconf.nairuby.orgです。ナイルビー。


http://rubyconf.nairuby.org/2018より

Rails公式ニュースにも掲載されている情報ですが、今年4月開催のピッツバーグのRailsカンファレンスCFPを募集だそうです(CFP: Call for Proposal)。


つっつきボイス:Bath Ruby Conferenceって何だ?っと思ったら、Bathはイギリスの地名なんだそうです」「水上温泉みたいな?」

Rails 5.2のdeprecation情報

ほとんど走り書きなので、5.2.0リリースまでに別途まとめようと思います。

「巨大なプルリク1件と細かいプルリク100件どっちがまし?」を考える(Hacklinesより)

今回たまたま見つけたHacklinesというRuby情報クローラが面白かったのでそこからいくつか記事を拾いました。


つっつきボイス: 「元記事にも貼られているこれほんに↓」「巨大なのがhorse-sizedで、こまいのがduck-sizedということみたいです」「コミットの粒度ってほんと悩みますね」「読まされるレビュアーの立場で考えるしかないかなー」

アセットのプリコンパイルを高速化するには(Hacklinesより)

「CDNを使う」「@importrequire_tree .を避ける」などの地道な方法が紹介されています。

マイグレーションを実行せずにSQLクエリを見たい(Hacklinesより)

同記事で、#31630 Allow to run migrations in check mode (dry run)というつい最近のPRが紹介されています。まだmergeされていません。


つっつきボイス: 「マイグレーションのdry run、たまに欲しくなりますよね」

ぼっち演算子&.の方がObject#tryよりずっと高速(Hacklinesより)

ベンチマークコードと結果はGistにあります。

#同Gistより
       user     system      total        real
      check for nil:  0.040000   0.000000   0.040000 (  0.040230)
   check respond_to:  0.100000   0.000000   0.100000 (  0.101780)
             rescue:  2.080000   0.020000   2.100000 (  2.103482)
 active_support try:  0.150000   0.000000   0.150000 (  0.151765)
    safe navigation:  0.040000   0.000000   0.040000 (  0.040369)

つっつきボイス: 「safe navigation operatorってぼっち演算子のことなのね」「後者はRubyでの俗称というかあだ名っぽいですね」
「ところでぼっち演算子って.&&.のどっちでしたっけw」「わかるーw: ワイもよく迷う」
「そういえばtry!ってどう違うんだったかな」「この記事↓翻訳したときにbabaさんに教えてもらったのを末尾に追加してあります: 『ぼっち演算子が#try!と少し異なるのは、引数付きだとnilのときに引数が評価されないという点です。』」「引数があるかどうかで違う、と」

Railsの`Object#try`がダメな理由と効果的な代替手段(翻訳)

追伸: 体操座りしながら指でいじいじしている形で覚えるとよいそうです。

belongs_to関連付けクエリのリファクタリング(RubyFlowより)

「ここではスコープよりクラスメソッドの方が自分にはわかりやすかったから」だそうです。

# 同記事より
class Job < ActiveRecord::Base
  belongs_to :category

  def self.publishable
    joins(:category).merge(Category.publishable)
  end
end

Job.publishable

他にEncapsulating queries in a Rails Modelという記事もありました。


つっつきボイス: 「スコープかクラスメソッドか」「scopeは最後のリファクタでそれっぽければやればいい気がする: デフォルトはclassメソッドでいーんじゃないかな?」

マイクロサービスはチームを苦しめる(Hacklinesより)


つっつきボイス: 「記事にあったコンウェイの法則ってこれですね↓」「うんうん、官僚組織のシステムはやっぱり官僚っぽい設計になるし」

Conwayの法則とは,“組織の設計するシステムには … その組織のコミュニケーション構造をそのまま反映した設計になるという制約がある”,というものだ。つまり,チームの開発成果がその組織の内部的なコミュニケーションのあり方によって決まる,という意味である。
Conwayの法則に従った組織の成長より

参考: クックパッドとマイクロサービス — Conwayの法則に言及しています

「ところでコンウェイっていうとライフゲームの英語名Conway’s Game of Lifeを思い出しちゃいます(年バレ!)」

flag_shih_tzu: Integerカラムにビットパターンでフラグを追加するgem(Hacklinesより)

# 同記事より
class Spaceship < ActiveRecord::Base
  include FlagShihTzu

  has_flags 1 => :warpdrive,
            2 => :shields,
            3 => :electrolytes
end

shih tzuって何だろうと思ったら、中国産の犬種「西施犬」のようです。フラグとどう関連するのかは謎です。

ついでに、元記事タイトルは「博士の異常な愛情 または私は如何にして心配するのを止めて水爆を愛するようになったか」のもじりですね。

Ruby trunkより

早くもRubyに大量のコミット

PB memoさんのRubyコミット日記です。年明け早々に追いきれないほどのコミット大漁節です。


つっつきボイス: 「みんな冬休み取ったー?w」

そういえば以下の記事で、今後Rubyのリリース日が前倒しになるかもしれないという構想が語られていました。

また、クリスマスリリースはプレゼントという意味があるものの、家族を持つコミッターが増えてきたため、「少し前の22日や23日にしたほうがよいかもしれない」と語った。

Integer#powの法(modulo)が巨大な場合の結果がおかしい->修正

以下は12が正しいそうです。

irb(main):020:0> 12.pow(1, 10000000000)
=> 1
irb(main):021:0> 12.pow(1, 10000000001)
=> 1
irb(main):022:0> 12.pow(1, 10000000002)
=> 1

beginなしでdo-endブロックでrescue

1年前の変更なので2.5には反映済みです。

lambda do
  begin  #<= これがなくてもいいようになった
    raise 'err'
  rescue
    $! # => #<RuntimeError: err>
  end
end.call

つっつきボイス: 「自分もこのbeginなくていいと思う」「matzがためらいがちにacceptしてました↓」

Although I am not a big fan of this syntax, mostly because I don’t like fine grain exception handling.
But I found out many developers prefer the syntax. After some consideration, I decided to accept this.

SymbolとStringの違いに関するRDocを追加

/* 定数名、メソッド名、変数名はシンボルとして返される
*
*     module One
*       Two = 2
*       def three; 3 end
*       @four = 4
*       @@five = 5
*       $six = 6
*     end
*     seven = 7
*
*     One.constants
*     # => [:Two]
*     One.instance_methods(true)
*     # => [:three]
*     One.instance_variables
*     # => [:@four]
*     One.class_variables
*     # => [:@@five]
*     global_variables.last
*     # => :$six
*     local_variables
*     # => [:seven]
*
* Symbolオブジェクトは識別子を表す点がStringオブジェクトと異なる
* Stringオブジェクトはテキストやデータを表す
*/

つっつきボイス: 「この間この記事↓を公開した後の変更なので取り上げてみました」


Rubyのシンボルをなくせるか考えてみた(翻訳)

Ruby

Fukuoka Ruby Awardエントリー募集(1/31まで)(Ruby公式ニュースより)


www.ruby-lang.orgより

Ruby 3とJIT(Ruby Weeklyより)

Noah Gibbsさんの記事です。Optcarrotがoptimized modeで相当速くなっています。


engineering.appfolio.comより

Ruby 2.5のベンチマーク取ってみた

HexaPDFを使っています。


gettalong.orgより

Kernel#itselfにRubyの美学を見た

短い記事です。

# 同記事より
collection.each_with_object({}) { |item, accum| accum[item] = accum[item].to_i + 1 }
# ↓ここまで簡潔に書ける
collection.group_by(&:itself).transform_values(&:count)

RubyにCコード書いてメモリ共有してみた

# 同記事より
require 'inline'
class CHello
  inline do |builder|
    builder.include '<stdio.h>'
    builder.c 'int sumThem() {
      return 2 + 2;
    }'
  end
end

>> CHello.new.sumThem #=> 4

つっつきボイス: 「RubyコードにまるっとCのコードがインラインで埋まっているんですよね」「これマジ凄くない?C拡張より楽チンそう」「rubyinlineでできるみたいです」「メモリ共有にはFiddle::Pointerを使ってるそうです」

「この記事にはネタ画像がいくつか埋まってるんですが、その中でもこれ↓: シャイニングっていう昔のくっそ怖い映画の一番有名なシーンなんですが」「なんか見たことあるっちゃある感じ」


blog.rebased.plより

「この『Here’s Johnny!!』っていうセリフは、実はこの場面までの緊張感を一発で台無しにする、英語圏のこの年代の人じゃないとわからないずっこけネタなんですね」「Tonight Showという米国の長寿テレビ番組のオープニングで司会者が必ず言うセリフなんですが、日本に置き換えるとさしずめ『サザエでございま~す』とか『ぼーくドラえもん』っていう感じ: そこでそれ言うか!みたいな」

JRubyより速い


つっつきボイス: 「今見てみると2.5ががくっと遅くなってますね」「何かつっかえてるのかな?」

卜部さんの「HashDoS脆弱性との戦い」

Ruby実装の命名の由来

これもNoah Gibbsさんの記事です。

RubyBench: RubyやRailsのベンチマークサイト


rubybench.orgより


つっつきボイス:https://speed.python.org/みたいなのがRubyにもないかなと思って探したら見つかりました: 相当細かくベンチ取ってくれて楽しい」


rubybench.orgより

Ruby開発者のための5つの習慣(Ruby Weeklyより)


  1. RuboCopはいつどんなときでもかけろ
  2. git historyを汚すな
  3. お遊びプロジェクトを立ち上げてみろ
  4. Railsのソースコードを読め
  5. Railsガイドを「もう一度」読め

つっつきボイス: 「5…」「5…」

unlessのご利用は控えめに(Hacklinesより)

# 元記事より
# Example 1
unless something?
  # do something
else
  # do other thing
end

# Example 2
unless something? || another_thing?
  # do something
end

つっつきボイス:unless自体はいいけど確かにelseと一緒に使うとか勘弁w」

RubyとPythonのmixinを比較する(Hacklinesより)

みっちり長い記事です。

# 同記事より
class RunnerMixin:
    def max_speed(self):
        return 4


class SortaFastHero(RunnerMixin):
    """This hero can run, which is better than walking."""
    pass


class SortaFastMonster(RunnerMixin):
    """This monster can run, so watch out!"""
    pass

つっつきボイス:endがないと、どうもパンツ履き忘れたような気持ちになってw」「Pythonコードってブラウザからコピペしたはずみでインデント消えちゃったり」「それはコピペするなということかも」

地味すぎて誰も気がついていないCRuby 2.5の新機能

mruby/c1.1 RC2リリース

  • Procクラスの実装
  • sprintfメソッドの実装
  • .classメソッドの実装
  • RangeObjectのリファレンスカウント対応
  • StringObjectのバグ修正
  • 重複した数値処理の排除
  • Rubyによるクラスの定義とインスタンスメソッドの定義を実装

各種言語のハッシュマップ実装を比較

  • Python
  • Ruby
  • Java
  • Scala
  • Golang
  • C#
  • C++

この記事のサイドバーにあったNo Magic: Regular Expressionsという記事もつい気になってしまいました。

Graphql-batchとPromise.rb

Graphql-batchはShopifyのリポジトリですね。内部でPromise.rbを使っているそうです。


つっつきボイス: 「↓こんな感じでRubyでPromiseできるみたいです」

# lgierth/promise.rbより
require 'promise'

Promise.new
  .tap(&:fulfill)
  .then { Promise.new.tap(&:fulfill) }
  .then { Promise.new.tap(&:reject) }
  .then(nil, proc { |reason| p reason })

JavaScript: 5分でわかるPromiseの基礎(翻訳)

asakusa.rb新年会

現時点でまだ空席あるようです。

KitchenCI: 複数プラットフォームをサポートするCIサービス


kitchen.ciより

driver:
  name: vagrant

provisioner:
  name: chef_zero

platforms:
  - name: ubuntu-14.04
  - name: windows-2012r2

suites:
  - name: client
    run_list:
      - recipe[postgresql::client]
  - name: server
    run_list:
      - recipe[postgresql::server]

Chefが前提のようです。

SQL

DB設計カタログサイト


つっつきボイス: 「確かにこれ凄い!」「医療とかホテルとか、よくここまで集めた」「実用に即したDB設計ってなかなか見る機会ないですよね」

PostgreSQLが「DBMS of the year 2017」に輝く(Postgres Weeklyより)

https://db-engines.com/en/rankingというランキングサイトを元にしています。


db-engines.comより


つっつきボイス: 「MongoDBとかってDBMSなんですかね?」

JavaScript

面接で聞かれるES6理論クイズ10問(解答付き)

500人以上の技術面接で使われた問題だそうです。

  1. JavaScriptのスコープを説明し、スコープの例を知っている限り列挙せよ(6点)
  2. ホイスティングを例を挙げて説明せよ(6点)
  3. prototypeの役割を例を挙げて説明せよ(6点)
  4. 3の例を拡張してprototypeの継承を説明せよ(5点)
  5. 3の例をES6構文で書き直せ(6点)
  6. thisの値を説明せよ(6点)
  7. コンテキストバインディングを例を挙げて説明せよ(3点)
  8. =====の一般的な違いを説明せよ(6点)
  9. 変数がarrayかどうかをチェックする方法を述べよ(3点)
  10. 以下のコードのどこがおかしいかを説明し、修正せよ(4点)
if ( typeof x === 'object' ) {
    x.visited = true;
}

rearmed-js: JavaScriptのArrayなどをRuby風に書けるライブラリ

// westonganger/rearmed-jsより
var array = [];
var cb = function(val, i){ };
array.any(cb=null) // returns bool
array.all(cb=null) // returns bool
array.compact(badValues=[null, undefined, '']) // returns array, accepts array or splat arguments
array.dig(*args) // returns value, accepts splat arguments or array
array.each(function(val, i){ })
...

Sinon.js: JavaScriptでmockやstubを使うライブラリ

テスティングフレームワークに依存しないそうです。

// sinonjs.orgより
it("returns the return value from the original function", function () {
    var callback = sinon.stub().returns(42);
    var proxy = once(callback);

    assert.equals(proxy(), 42);
});

NectarJS: JSコードをネイティブバイナリにコンパイル(JavaScript Liveより)

WebAssemblyにも対応しているそうです。

via GIPHY

JavaScriptオブジェクトのrest/spreadプロパティ(JavaScript Liveより)

// 同記事より
const style = {
  width: 300,
  marginLeft: 10,
  marginRight: 30
};

const { width, ...margin } = style;

console.log(width);  // => 300
console.log(margin); // => { marginLeft: 10, marginRight: 30 }

JavaScriptのhoistingを理解する(JavaScript Liveより)


medium.com/@thamizhchelvan2005より

JavaScriptの「obfuscation」とは何か(JavaScript Liveより)

obfuscationは、いわゆるminifyやuglifyより徹底的に変換をかけています。

// 同記事より
function hello(name) {
console.log('Hello, ' + name);
}
hello('New user');

// obfuscation後
eval(function(p,a,c,k,e,d){e=function(c){return c};if(!''.replace(/^/,String)){while(c--){d=k||c}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k)}}return p}('3 0(1){2.4(\'5, \'+1)}0(\'7 6\');',8,8,'hello|name|console|function|log|Hello|user|New'.split('|'),0,{}))

tui.editor: 表やグラフも扱えるMarkdownエディタ(JavaScript Liveより)

表やUML図などを直接扱えるようです。


nhnent/tui.editorより

CSS/HTML/フロントエンド

HTML 5.2の新着情報とポイント(jser.infoより)


  • <dialog>要素
  • Apple製品でのアイコン表示改善
  • <main>要素を複数持てる
  • <body>タグ内にも<style>を書ける(ただしパフォーマンス上おすすめしない)
  • <legend>タグ内に見出しタグを置ける
  • 廃止: keygenmenumenuitem、厳密なDOCTYPE
  • etc

CSSの:notセレクタを導入


つっつきボイス::notときどき使いますヨ: 繰り返し要素の最後のところだけ区切り線入れたくないときとか便利」「そうそう、これないと不便」

その他

技術トークの5つのコツ


reverentgeek.comより

  • その技術を選んだ理由を話す
  • その技術で何ができるかを話す
  • どうやったら動いたかをデモする(しくじったポイントも入れよう)
  • 参考リンクを忘れずに
  • マイクはないものと思え

meltdown: メルトダウン脆弱性の実演コード(GitHub Trendingより)

今旬のネタだけあって、10日ほどで★2400超えです。

これマジで?

Go 1.10 Beta2リリース

番外

闇深そうなフォントかるた

ケンブリッジ大学の脳力測定サイト

いわゆる脳トレ的なやつです。

成功の秘訣は「大学の町の近くで育つこと」?

日本語記事: 元グーグルのデータサイエンティストが発見! 成功者の意外な共通点とは

340刷

ロシアのサーバールームお祓い事情


つっつきボイス: 「サーバールームで水撒くか普通…」

AIで転職情報を勝手にかき集めるのは…


つっつきボイス: 「この人に目をつけられたらもう逃げられないっすね」

闇落ち以外のパターンが思いつかない


今週は以上です。

バックナンバー(2017年後半)

週刊Railsウォッチ(20171222)定番gemまとめサイト、active_record-mtiでテーブル継承、PostgreSQL 10の非互換変更点、Railsガイド攻略法ほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

Awesome Ruby

RubyFlow

160928_1638_XvIP4h

Postgres Weekly

postgres_weekly_banner

Frontend Weekly

frontendweekly_banner_captured

Github Trending

160928_1701_Q9dJIU

新しいRailsフロントエンド開発(3)Webpackの詳細、ActionCableの実装とHerokuへのデプロイ(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

新しいRailsフロントエンド開発(3)Webpackの詳細、ActionCableの実装とHerokuへのデプロイ(翻訳)

前書き

本記事は、フロントエンドのフレームワークに依存しないRailsプレゼンテーションロジックを現代的かつモジュール単位かつコンポーネントベースで扱う方法を独断に基いて解説するガイドです。3部構成のチュートリアルで、例を元に最新のフロントエンド技術の最小限に学習し、Railsフロントエンド周りをすべて理解しましょう。

Part 2までのおさらい

こちらもお読みください:

Part 2までに、「コンポーネント」アプローチを用いてチャットアプリの骨格を組み立てました。各コンポーネントは、アプリのfrontend部分の内部のフォルダとして表現されており、それぞれが.erbパーシャル、.cssスタイルシート、.jsスクリプトの3つのファイルで構成されています。現時点でJavaScriptコードに含まれているのは、ネストしたコンポーネントを読み込むためのimport文だけです。これによってすべてのパーツがapplication.jsのエントリポイントとして含まれるようになり、Webpacker gemでこれらをまとめてCSSやJSのバンドルをビルドできるようになっています。

今回のチュートリアルの最後の章では、JavaScriptを用いてチャットが動くようにする予定です。公式のRailsドキュメントは未だにSprocketsやCoffeeScriptが前提になっているため、ActionCableをES6モジュールから用いる方法についても解説します。

「sprockets抜き」アプリが完成したら、Herokuにデプロイします。

完成版のEvil Chatアプリのコードをすぐにもご覧になりたい場合はGitHubのリポジトリをどうぞ。

ご存知かと思いますが、ActionCableの理解はそれほど簡単ではありませんので、できるだけ手順ごとに動作を明示的に解説してみます。経験豊富な開発者の知性を過小評価する意図はありませんのでご了承ください。途中でActionCableを十分理解できた方は、解説をスキップしてコードスニペットまで進めてください。コードスニペットは通常のSprockets実装と異なっているため、Railsガイド(訳注: 英語版Edgeガイドです)のコード例はWebpackで動作しません。

ActionCableのRuby部分

まずは、チャットのチャンネルの生成が必要です。

$ rails g channel chat

これでapp/channels/の内部にchat_channel.rbというファイルが作成されます。

ActionCableはRailsでWebSocketsと統合されており、サーバー側のロジックをRubyで書き、クライアント側のロジックをJavaScriptで書くことができます。ActionCableのクールな点は、ブラウザ上で実行されるJavaScriptから、サーバー側のRubyメソッドを呼び出せることです。chat_channel.rbはチャット用のメソッドを定義する場所であり、全登録ユーザーのデータのストリーミング(本チュートリアルの場合、新しいメッセージでDOMを更新する少量のHTMLです)も担当します。

チャンネル固有の機能を扱う前に、ActionCableが認証済みユーザーのみをブロードキャストすることを担保する必要があります。アプリ作成時に生成したapp/channels/application_cableフォルダの内部を見ると、WebSockets認証を担当するconnection.rbファイルがあります。Part 2の認証が非常にシンプルだったことを思い出しましょう。sessionハッシュ内に単にusernameキーを作成し、ユーザーがどんなusernameでも使えるようになっていました。以下は今回必要なコードです。

# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = request.session.fetch("username", nil)
      reject_unauthorized_connection unless current_user
    end
  end
end

ここではセッションからusernameを取り出そうとしています。usernameがない場合、接続を拒否します。実際には、新しいユーザーは「log in」画面を経由するまでActionCableのブロードキャストを受け取りません。

続いてchat_channel.rbに手を加えます。

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat"
  end

  # サーバーがメッセージ形式のコンテンツを受け取ると呼び出される
  def send_message(payload)
    message = Message.new(author: current_user, text: payload["message"])
    if message.save
      ActionCable.server.broadcast "chat", message: render(message)
    end
  end

  private

  def render(message)
    ApplicationController.new.helpers.c("message", message: message)
  end
end

subscribedメソッドは接続が認証されると呼び出されます。stream_fromは、「chat」チャンネルでブロードキャストされるどんなメッセージでもクライアントに到達できるということを表します。

このsend_messageメソッドは最も興味深い部分です。今はアプリのRuby部分の内部なので、ActiveRecordと直接やり取りできます。私たちのシンプルな例では、「メッセージを1件送信する」というのは、Messageモデルの新しいインスタンスを1つ作成してデータベースに保存し、authortextが正しく設定されたmessageパーシャルをレンダリングして、生成されたHTMLを「chat」チャンネルでブロードキャストするということを意味します。

ここでご注意いただきたいのは、app/channelsの内部からはApplicationControllerrenderメソッドにも、コンポーネントをレンダリングするカスタムcヘルパーにも直接アクセスできないという点です。そこで、ヘルパーを間接的に呼び出す別のrender定義を作成します。そのために、ApplicationControllerのインスタンスを1つ作成して、ApplicationHelperモジュールで定義したヘルパーにアクセスします。今私たちが関心を抱いているのはcヘルパーなので、ApplicationController.new.helpers.cでアクセスします。

ActionCableのJavaScript部分

純粋なrails newで生成したRails 5.1アプリでは、ActionCableのクライアント部分(JavaScriptで記述されている)はアセットパイプラインでインクルードされます。私たちがapp/assetsを削除したときに、この標準的な実装も効率よく取り除かれていますので、ActionCableのJavaScriptライブラリを再度インストールする必要があります。今度はYarn経由でnpmからインストールします。

$ yarn add actioncable

さて、WebpackでActionCable(あるいは別のJavaScriptライブラリ)を用いる場合の特別な点とは何でしょうか?

Sprocketsを使うと、JavaScriptファイルが結合後に共通のスコープで共有されたものを扱うことになるため、this.jsで宣言されたものは何であってもthis.jsが事前に読み込まれていればその後のthat.jsからアクセスできます。Webpackはこの点が違っており、より抑制の効いたアプローチを採用しています。Ross Kaffenbergerの良記事から引用します。

これは、ブラウザでのJavaScriptバンドル方法のパラダイムがSprocketsとWebpackで根底から異なっていることを理解するうえで役立ちます。
この違いは、Webpackの動作の中核部分にあります。Webpackでは、SprocketsのようにJavaScriptコードをグローバルスコープで結合するのではなく、個別のJavaScriptモジュールをクロージャ経由で個別のスコープに仕切っているので、モジュール間のアクセスをimport経由で宣言することが必須になります。これらのJavaScriptモジュールは、デフォルトでは一切グローバルスコープには公開されません。

私たちはES6のexport文やimport文を多用しなければならなくなります。しかし私たちは、最初にfrontend内にclientフォルダを作成しています。ActionCableの(JavaScript)クライアントはここに置きます。

$ mkdir frontend/client
$ touch frontend/client/cable.js

cable.jsは、「cable」コネクションのconsumerインスタンスの作成に使われます。Sprocketsで書かれた標準的なRailsサンプルでは、これはグローバルなAppオブジェクトの一部として作成されるのが普通です。公式のActionCableドキュメントや世にあまたあるチュートリアルでは次のようなコードが使われています。

// これはコピペしてはいけません!
(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();
}).call(this);

このコード例を、私たちのモジュールベースのシステムに合わせて調整する必要があります。また、consumerが作成済みの場合には既存のコネクションを再利用してcreateConsumer関数の再呼び出しを避けたいと思います。そのためにグローバルなwindow変数を使いたくないので、別のアプローチを採用します。私たちのcable.jsモジュールは、コネクションのインスタンスをconsumer内部変数に保持し、createChannel関数をexportします。この関数は既存のconsumerをchatチャネルにサブスクライブするか、新しいconsumerインスタンスを1つ作成します。それではコードをcable.jsに書いてみましょう。

// frontend/client/cable.js
import cable from "actioncable";

let consumer;

function createChannel(...args) {
  if (!consumer) {
    consumer = cable.createConsumer();
  }

  return consumer.subscriptions.create(...args);
}

export default createChannel;

createChannel関数は汎用なので、consumerを特定のチャンネルにサブスクライブしたいどんな箇所からでも正しい引数を与えて使うことができます。したがって、サーバー側のchat_channel.rbのRubyコードに対応するクライアント側JavaScriptコードとなるファイルが別途必要になります。このファイルをchat.jsと呼ぶことにしましょう。

$ touch frontend/client/chat.js

コードは次のとおりです。

// frontend/client/chat.js
import createChannel from "client/cable";

let callback; // 後で関数を保持するための変数を宣言

const chat = createChannel("ChatChannel", {
  received({ message }) {
    if (callback) callback.call(null, message);
  }
});

// メッセージを1件送信する: `perform`メソッドは、対応するRubyメソッド(chat_channel.rbで定義)を呼び出す
// ここがJavaScriptとRubyをつなぐ架け橋です!
function sendMessage(message) {
  chat.perform("send_message", { message });
}

// メッセージを1件受け取る: ChatChannelで何かを受信すると
// このコールバックが呼び出される
function setCallback(fn) {
  callback = fn;
}

export { sendMessage, setCallback };

この部分は難解なので、説明のテンポを落としてじっくり見てみましょう。
細かな動作は次のようになっています。

  • cable.jsからcreateChannel関数をimportします。
  • この関数に2つの引数を与えて呼び出します。チャンネルの名前(Rubyのsome_channelのような名前はJavaScriptではSomeChannelとし、両者の命名慣習を壊さないようにしなければならない点に注意)と、ActionCableの標準コールバック(connecteddisconnectedreceived)を定義するオブジェクトです。ここで必要なのはreceivedコールバックのみです。このコールバックは、ブロードキャストされたデータをJavaScriptオブジェクトの形式として引数として持つチャンネルブロードキャストをconsumerが受け取ると呼び出されます(RubyとJavaScriptオブジェクトの変換はRails自身が行います)。
  • ここから少々ややこしくなります。messageオブジェクトを受信したら、何らかの関数を呼び出す必要があります。コンポーネントのこの部分は、必要に応じてDOMを扱う方法を責務上知っていなければならないので、この関数をここで定義したくありません。そこで、setCallbackという汎用的な関数を1つ作成します。この関数は、正しいコンポーネントから呼び出されると、メッセージ受信後に呼び出したいコンポーネント固有のあらゆる関数を保存するcallback変数を変更します。
  • sendMessageは、コネクションインスタンスのperformメソッドを呼び出します。ここはActionCableの最も魔術的な部分であり、JavaScriptからRubyのメソッドを呼び出します。これはchat_channel.rbからsend_messageメソッドをトリガして、messageオブジェクトを引数として渡します。この{ message }という記法は、ES6の{ message: message }のショートハンドです。ここではペイロードがmessageキーの下にあることを前提としています。このコンテキストにおける「message」は、メッセージフォームに含まれるユーザー(visitor)の種類を表す単なるテキストです。
  • 最後に、モジュールからsendMessagesetCallbackを両方ともexportし、後でコンポーネントで使えるようにします。

明確なメッセージを1件送信する

それでは最初にメッセージの送信を扱いましょう。この責務を引き受けるべきコンポーネントはどれでしょうか?Part 2では、個別のメッセージ用にmessageコンポーネントを、メッセージのリスト用にmessagesコンポーネントを、テキストの送信にはmessage-formを使いました。ブルーの大きな「Send」ボタンはmessage-formの内部にあるので、ここに置くのが正解です。frontend/components/message-form/message-form.jsのコードを変更しましょう。

// frontend/components/message-form/message-form.js

// client/chat.jsからsendMessageをimportする必要がある
import { sendMessage } from "client/chat";
import "./message-form.css";

const form = document.querySelector(".js-message-form");
const input = form.querySelector(".js-message-form--input");
const submit = form.querySelector(".js-message-form--submit");

function submitForm() {
  // sendMessageを呼び出し、その結果Rubyのsend_messageメソッドが呼ばれて
// ActiveRecordでMessageインスタンスが作成される
  sendMessage(input.value);
  input.value = "";
  input.focus();
}

// コマンドキー(またはCtrlキー)+Enterでメッセージを送信できる
input.addEventListener("keydown", event => {
  if (event.keyCode === 13 && event.metaKey) {
    event.preventDefault();
    submitForm();
  }
});

// ボタンをクリックして送信してもよい
submit.addEventListener("click", event => {
  event.preventDefault();
  submitForm();
});

動作を確認しましょう。もう一度サーバーを起動して認証し、メッセージボックスに適当なテキストを入力してコマンド+Enterキーを押し、Railsログを調べると次のように表示されます。

chat_channelの最初のブロードキャスト

chat_channelの最初のブロードキャスト

これで、フォームを送信すると、バックエンドでMessageインスタンスが新たに1つ作成され、メッセージのパーシャルが生成されてActionCableですべての登録ユーザーにブロードキャストされます。残るは、HTMLで受け取った文字列をDOMに挿入してページに表示するだけです。

受信したメッセージ

新しいメッセージをその都度動的にページに挿入する責務を負うのはmessagesコンポーネントです。元々このコンポーネントはデータベース内のすべてのメッセージをレンダリングする責務を負っていることがその理由です。

ここで行う必要があるのは、chat.jsモジュールのsetCallback関数を呼び出して、ブロードキャストされたメッセージを引数として受け取る別の関数に渡すことだけです。もう一度おさらいしましょう。chat.jsモジュールは、chatチャンネルで何かがブロードキャストされると、常にreceivedイベントに対して何か操作を行える状態になりますが、正確な操作については(明示的に示すまでは)関知しません。これを行うには、実行したい関数をsetCallbackに渡します。

messages.jsの新しいコードは次のとおりです。

// frontend/components/messages/messages.js
import { setCallback } from "client/chat";
import "components/message/message";
import "./messages.css";

const messages = document.querySelector(".js-messages");
const content = messages.querySelector(".js-messages--content");

function scrollToBottom() {
  content.scrollTop = content.scrollHeight;
}

scrollToBottom();

// ActionCableで新しいメッセージを1件受け取るたびに
// このコード片を呼び出すよう`chat.js`に伝える
setCallback(message => {
  content.insertAdjacentHTML("beforeend", message);
  scrollToBottom();
});

ここでchat.jsモジュールに渡しているのは、メッセージのリストを上にスクロールして、新しいメッセージのHTMLを下に追加するだけのシンプルな関数です。これで、2種類の異なるブラウザを立ち上げて、それぞれ別のニックネームでログインしてチャットしてみると、以下のようにすべて正常に動作していることがわかります。

異なるブラウザで動作するチャット

異なるブラウザで動作するチャット

Herokuにデプロイする

いよいよアプリをHerokuにデプロイして、本番環境でもチャットできることを確認しましょう。最初にHerokuアカウントを用意し、自分のPCにHeroku CLIがインストールされていることを確認します。これでターミナルでherokuコマンドが使えるようになります。

アプリのデプロイを準備するうえで必要な点がいくつかあります。

最初に、既存のProcfilerails serverwebpack-dev-serverの実行に使われる)をProcfile.devに変更します。devなしのProcfileはHerokuで使います。また、本番環境ではwebpack-dev-serverが実行されないようにしたいと思います。

Procfile.devは次のようになります。

server: bin/rails server
assets: bin/webpack-dev-server

メインのProcfileにはserver行だけを残します。

server: bin/rails server

注意: この変更を行った後でアプリをlocalhostで実行したい場合は、hivemind Procfile.dev(使っているプロセスマネージャによってはovermind s -f Procfile.devforeman run -f Procfile.devなど)で起動する必要があります。

次に、ビルドタスクがHeroku側で認識されるようにする必要があります。

RubyアプリをプッシュしていることがHeroku側で認識されると、assets:precompileを起動しようとします。これはアセットパイプラインでアセットをビルドするのに昔から使われているタスクです。しかしWebpackerを使う場合は、別のyarn:installタスクとwebpacker:compileタスクを呼び出す必要があります。

最新バージョンのRailsとWebpacker(3.2.0)は、Sprocketsを無効にしてあってもassets:precompileでSprocketsを起動できます(試しにローカルでbundle exec rails assets:precompileを実行してみると、パッケージがビルドされてpublicフォルダに置かれる様子を見ることができます)。

ただし本記事執筆時点では、Rails 5.1.4とWebpacker 3.2.0による「Sprockets抜き」アプリのHerokuでのビルドは失敗しました。Vladimir Dementyevのおかげで回避方法がわかりました。Rakefileで明示的にassets:precompileを定義する必要があります。

# Rakefile
require_relative 'config/application'

# この行を追加
Rake::Task.define_task("assets:precompile" => ["yarn:install", "webpacker:compile"])

Rails.application.load_tasks

RailsとWebpackerのコントリビューターは現在も本番環境でのアセットのビルドをできるだけ楽にする最善の方法を模索中なので、この部分は将来変更される可能性があります。すべてが落ち着いて、追加のハックなしでHerokuでアプリをビルドできるようになれば理想です。

また、HerokuでActionCableを動かすためには本番でRedisを有効にする必要もあります。Gemfileのgem 'redis', '~> 3.0'のコメントを解除してください(注意: バージョン4はRails 5.1のActionCableで認識されません: 5.2で修正予定)。

config/cable.ymlproductionに、urlの正しい設定が含まれていることを確認します。

development:
  adapter: async

test:
  adapter: async

production:
  adapter: redis
  url: <%= ENV["REDIS_URL"] %>
  channel_prefix: evil_chat_production

REDIS_URL環境変数に正しいRedisサーバーのURLを設定するために、Heroku Redisアドオンを使います。

そして最後に、config/environments/production.rbに以下の行を追加してください。

config.secret_key_base = ENV["SECRET_KEY_BASE"]

secrets.ymlをソースコントロールにコミットしない場合は、この行が必要です(ただしRailsの「encrypted secrets」を設定していない場合はこの行を追加すべきではありません)。

ついにデプロイ準備ができました。

$ heroku create YOUR_APP_NAME
$ heroku addons:create heroku-redis:hobby-dev

数分後にHeroku Redisアドオンが作成されたら(heroku addons:infoでステータスを確認できます)、次を実行します。

$ git add . && git commit -m "prepare for deploy"
$ git push heroku master

アプリのビルドが完了したら、heroku run rails db:migrateを実行してproductionのデータベースを準備します。すべてうまくいけば、デプロイしたアプリをheroku openでブラウザに表示できます。

うまく動いた方、おめでとうございます!

補足: 静的なアセットについて

今回ビルドしたアプリでは静的なアセットを使っていませんが、Webpackerで静的なアセットを扱う方法についても触れておく価値があると思います。ここでは画像を扱いたいとしましょう。最初に、画像の置き場所を決める必要があります。frontendフォルダの下のimagesフォルダにまとめて置くか、画面の表示を担当するコンポーネントの下に個別の画像を置きます。画像をどこに置くとしても、画像がWebpack manifestに現れるようにするには、画像をJavaScriptにimportして最終的にapplication.jsのエントリポイントに含まれるようにする必要があります。

app/assets/imagesの下にある既存の画像をすべてfrontend/staticに素早く移動してstatic.jsエントリポイントにリンクする方法については、Gistをご覧ください。

画像の数が多すぎて、ヘルパーモジュールのバンドル項目を増やしたくない場合(Webpackのfile-loaderは、ファイルごとにパスを返す責任だけを持つモジュールを1つ生成します)、packsの下に個別のエントリポイントを作成して(static.jsなどのように)呼び出すこともできます。

そして、asset_pack_pathヘルパーimage_tagを組み合わせると、正しい<img src="">を生成できます。

画像とコンポーネントをまとめる方法は次のような感じになります。

  • フォルダ構造:
frontend/components/header
├── _header.html.erb
├── header.css
├── header.js
└── static
    └── logo.png

header.jsは次のようになります。

import "./header.css";
import "./static/logo.png"

これで次のようにERBパーシャルに書けます。

<%= image_tag asset_pack_path('./static/logo.png') %>

別の方法としては、image_tagを使うのを我慢し、代わりにCSSでurlヘルパーを用いてWebpackのcss-loaderがデフォルトでプロジェクトに含める画像を直接読み込む方法もあります。これで、次のようにCSSのbackground-プロパティとして要素に画像を割り当てることができます。

.header {
  &--logo {
    width: 100px;
    height: 100px;
    margin-bottom: 25px;
    background-image: url("./static/logo.png");
    background-size: 100%;
  }
}

この方法にする場合、JavaScriptファイルで画像をimportする必要も生じません。なお、url()はフォントにも使えます。

プロジェクトのリポジトリには、SVGアイコンをCSSから読み込む例も含まれています。インラインSVGを使いたい場合は、postcss-inline-svgモジュールを使うこともできます。

「Sprockets抜き」をやってみてわかったこと

ActionCableを使った場合とまったく同様に、RailsでSprocketを無効にすると他のいくつかの部分についてもnpmで再インストールする必要が生じます。

  • Turbolinks

プロジェクトでTurbolinksを再度有効にするには以下のようにします。

$ yarn add turbolinks
// frontend/packs/application.js
import Turbolinks from "turbolinks";
Turbolinks.start();
  • UJS

RailsにSprocketsがない場合、次のようにnpmrails-ujsを再インストールしないとUnobtrusive JavaScriptを理解できなくなります(link_tomethod: :deleteの設定など)。

$ yarn add rails-ujs
// frontend/packs/application.js
import Rails from "rails-ujs";
Rails.start();

本チュートリアルからヒントを得たプロジェクトの紹介

  • Komponentは、本記事で解説した「コンポーネントベースのアプローチ」をRailsプロジェクトに取り入れやすくするgemです。このgemに含まれるジェネレーターは、frontendフォルダの作成、Webpacker configの変更、コンポーネント作成を単一のコマンドで行なえます。また、パーシャルにふさわしいテンプレートエンジンを検出したり、コンポーネントごとの「プロパティ」やヘルパーの設定に使える.rbファイルでコンポーネントを拡張したりします。

Komponent gemの作成とメンテナンスは、フランスの開発会社OuvragesEtamin Studioが、Evil Martiansとは独立に行っています。


お読みいただきありがとうございました!

本チュートリアル3部作(全貌を理解するにはすべてお読みください)では、Webpackerを完全に採り入れてアセットパイプラインを取り除き、Reactなどのフロントエンドフレームワークについて学ばずに、できるだけRailsの組み込みツールを用いて「コンポーネント」のコンセプトに基づいてRailsのフロントエンドコードを編成する方法を学びました。本チュートリアルで作ったシンプルなチャットアプリは、Evil Martiansによって現実のプロジェクトで積極的に用いられている方法でデプロイ可能です。

本チュートリアルを進めるうえで何か問題がありましたら、お気軽にGitHubのissueを開いてお知らせください。


Part 1 | Part 2 | Part 3

スタートアップをワープ速度で成長させられる地球外エンジニアよ!Evil Martiansのフォームにて待つ。

関連記事

新しいRailsフロントエンド開発(1)Asset PipelineからWebpackへ(翻訳)

新しいRailsフロントエンド開発(2)コンポーネントベースでアプリを書く(翻訳)

Vue.jsサンプルコード(29)フィールドごとに全角英数字入力と半角英数字入力を自動で切り替える

$
0
0

29. フィールドごとに全角英数字入力と半角英数字入力を自動で切り替える

  • Vue.jsバージョン: 2.5.2
  • [全角]フィールドに入力する英数字は全角に、[半角]フィールドに入力する英数字は半角になります。
  • 日本語入力には影響しません。
  • 画面をリロードすると最初の状態に戻ります。

サンプルコード

ポイント

watchで変数の変更を監視しています。watchの内部で変数をさらに変更しても無限ループになりません。

  const vm = new Vue({
    el: "#app",
    data: {a: "", b: ""},
    watch: {
      a: function(v) {
        this.a = v.replace(/[A-Za-z0-9]/g, function(s) { return String.fromCharCode(s.charCodeAt(0) + 65248) })
      },
      b: function(v) {
        this.b = v.replace(/[A-Za-z0-9]/g, function(s) { return String.fromCharCode(s.charCodeAt(0) - 65248) })
      },
    },
  })

バックナンバー(Vue.jsサンプルコード)

Vue.jsサンプルコード(01〜03)Hello World・簡単な導入方法・デバッグ・結果の表示とメモ化

週刊Railsウォッチ(20180119)derailed_benchmarks gem、PostgreSQLをGraphQL API化するPostGraphile、機械学習でモック画像をHTML化ほか

$
0
0

こんにちは、hachi8833です。Nintendo Laboにいろいろ持ってかれそうで気になってます。


つっつきボイス: 「任天堂のものづくりセンス、パないなー」

それでは今週のウォッチ、いってみましょう。

Rails: 今週の改修

今回はCommit差分から見繕いました。

left_outer_joinsをunscopeできるようになった

# activerecord/lib/active_record/relation/query_methods.rb#351
     VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
                                      :limit, :offset, :joins, :includes, :from,
-                                     :readonly, :having])
+                                     :readonly, :having, :left_outer_joins])

つっつきボイス: 「scopeでleft_outer_joinsできるならunscopeもできないと、ってことかな」

重要度の低いダイジェストにデフォルトでSHA-1を使用するようになった

# railties/lib/rails/application/configuration.rb#103
           if respond_to?(:active_support)
             active_support.use_authenticated_message_encryption = true
+            active_support.use_sha1_digests = true
           end

ETagヘッダーなどの重要でないダイジェストにはMD5ではなくSHA-1を使う。
Rails.application.config.active_support.use_sha1_digests = true
new_framework_defaults_5_2.rb.ttより大意

pg-1.0 gemに対応

pgが0.21から1.0にメジャーバージョンアップしたそうです。

# Gemfile.lock#343
-    pg (0.19.0)
-    pg (0.19.0-x64-mingw32)
-    pg (0.19.0-x86-mingw32)
+    pg (1.0.0)
+    pg (1.0.0-x64-mingw32)
+    pg (1.0.0-x86-mingw32)

つっつきボイス: 「へー、pgはもう永遠に1.0にならないんじゃないかと思ってた」「queue_classicってメンテナ代わったのかな?↓」

# Gemfile#65
-  gem "queue_classic", github: "QueueClassic/queue_classic", branch: "master", require: false, platforms: :ruby
+  gem "queue_classic", github: "Kjarrigan/queue_classic", branch: "update-pg", require: false, platforms: :ruby

savesave!の後でオブジェクトがunfreezeされていたのを修正

破棄したオブジェクトがsave後に変更される可能性があったので修正されました。

# activerecord/lib/active_record/persistence.rb#65
     def create_or_update(*args, &block)
       _raise_readonly_record_error if readonly?
+      return false if destroyed?
       result = new_record? ? _create_record(&block) : _update_record(*args, &block)
       result != false
     end

つっつきボイス: 「これ本当ならエラーをraiseしたいところだろうな: 互換性とかの問題でfalseを返してるのかも」

MySQL: create_databasecollationが指定されている場合にデフォルトのcharsetを追加しないように修正

# activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb#250
       def create_database(name, options = {})
         if options[:collation]
-          execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
+          execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
         else
           execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
         end

つっつきボイス: 「そうそう、知らずに|| 'utf8'が効いちゃうとハマるんだよなー」「修正後のテストでは寿司ビール対策でおなじみのutf8mb4_bin使ってますね」

MySQLのencodingをutf8からutfmb4に変更して寿司ビール問題に対応する

リファクタリング: Browserクラスを新設

システムテストのactionpack/lib/action_dispatch/system_testing/driver.rbのオプションがBrowserクラスに引っ越しました。

# actionpack/lib/action_dispatch/system_testing/browser.rb
+module ActionDispatch
+  module SystemTesting
+    class Browser # :nodoc:
+      attr_reader :name
+
+      def initialize(name)
+        @name = name
+      end
...

DHHによる修正2件

# rails/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt#9
   policy.font_src    :self, :https, :data
   policy.img_src     :self, :https, :data
   policy.object_src  :none
-  policy.script_src  :self, :https
+  policy.script_src  :self, :https, :unsafe_inline
   policy.style_src   :self, :https, :unsafe_inline

   # Specify URI for violation reports

つっつきボイス: 「CSP=コンテンツセキュリティポリシー」「unsafe_inlineはW3Cのこれですね↓」

; Keywords:
keyword-source = “‘self'” / “‘unsafe-inline'” / “‘unsafe-eval'” / “‘strict-dynamic'” / “‘unsafe-hashed-attributes'”
CSP3より

-Rails.application.config.content_security_policy do |p|
-  p.default_src :self, :https
-  p.font_src    :self, :https, :data
-  p.img_src     :self, :https, :data
-  p.object_src  :none
-  p.script_src  :self, :https
-  p.style_src   :self, :https, :unsafe_inline
+Rails.application.config.content_security_policy do |policy|
+  policy.default_src :self, :https
+  policy.font_src    :self, :https, :data
+  policy.img_src     :self, :https, :data
+  policy.object_src  :none
+  policy.script_src  :self, :https
+  policy.style_src   :self, :https, :unsafe_inline

つっつきボイス: 「少なくともpはないなー: Kernel.#pがあるから」「そういえば1文字のローカル変数で他にも使えないものがあったような…」

その後思い出しましたが、pryではcなどをローカル変数に使うと怒られるのでした。

[1] pry(main)> c=1
=> 1
[2] pry(main)> c
Error: Cannot find local context. Did you use `binding.pry`?

参考: Pryのコンソールで使えない変数

Rails

Railsチュートリアルが5.1.4に対応


つっつきボイス: 「安川さんたちが継続的翻訳システムを構築しているおかげでRailsチュートリアルもガイドもオープンな差分翻訳ができるようになっててうれしいです: 自分はバッチで翻訳する方が好きですが」

プロセスマネージャ再び

Dogfooding Process Managerの続きだそうです。


つっつきボイス: 「自前でプロセスマネージャをこしらえた話のようなんですが、このプロセスって何だろうと思って」「ざっとしか見てないけど、Unixのプロセスのことではなさそうに見える」「ところで、何とかmanagerってネーミングはたいていアンチパターンですね」「あー確かに」

そういえば野球の世界では監督はmanagerですが、日本だとマネージャーは違う意味に横滑りしてますね。

マイグレーションをpendingしたままRailsを本番で実行しないようにする方法

短い記事です。ActiveRecord::Migration.check_pending!でやれるそうです。

# 同記事より
if ($PROGRAM_NAME.include?('puma') || $PROGRAM_NAME.include?('sidekiq')) && Rails.env.production?
  ActiveRecord::Migration.check_pending!
end

RailsのForm Objectとルーティング(RubyFlowより)

# 同記事より
class NewQuestionnaireForm
   include ActiveModel::Model

  def to_model
    Questionnaire.new(title: title, questions: questions)
  end

  def save
    to_model.save
  end
end

Railsのメモリ容量を減らしてHeroku課金を節約(Awesome Rubyより)


同記事より

以下の記事に出てきたjemallocyajl-rubyなどを動員して節約に励んでいます。

Ruby: mallocでマルチスレッドプログラムのメモリが倍増する理由(翻訳)

⭐derailed_benchmarks: Railsアプリのさまざまなベンチマークを取れるgem⭐

上の記事にも使われていたgemで、★1800超えです。ヒープダンプ/メモリリーク調査/stackprofなどさまざまな静的/動的情報を取れます。

# READMEより
$ bundle exec derailed exec perf:stackprof
==================================
  Mode: cpu(1000)
  Samples: 16067 (1.07% miss rate)
  GC: 2651 (16.50%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
      1293   (8.0%)        1293   (8.0%)     block in ActionDispatch::Journey::Formatter#missing_keys
       872   (5.4%)         872   (5.4%)     block in ActiveSupport::Inflector#apply_inflections
       935   (5.8%)         802   (5.0%)     ActiveSupport::SafeBuffer#safe_concat
       688   (4.3%)         688   (4.3%)     Temple::Utils#escape_html
       578   (3.6%)         578   (3.6%)     ActiveRecord::Attribute#initialize
...

つっつきボイス: 「derailed_benchmarksは結構使われている印象っすね」「作者はRichard Schneemanさんでした」

ベテランRubyistがPythonコードを5倍速くした話(翻訳)

今週の⭐を進呈いたします。おめでとうございます。

Stimulus: Turbolinksと相性のよい控えめなJSフレームワーク(Ruby Weeklyより)


stimulusjs/stimulusより

// hello_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  greet() {
    console.log(`Hello, ${this.name}!`)
  }

  get name() {
    return this.targets.find("name").value
  }
}

つっつきボイス: 「Stimulusって、確かDHHのBasecampがやってるやつですよね」「Turbolinksって最近オフにすること多いけど」

Railsを順を追ってアップグレードする(Awesome Rubyより)


つっつきボイス: 「冒頭の1. Stop the world、2. Long-lived upgrade branchとかまさに定番のアプローチ」「こうやって本番を二本立てにして↓リバースプロキシで振り分けながら少しずつ移行するというのもあるある: 検証が不十分なまま切り替えるとえらいことになったりするけど」


同記事より

ActiveRecordに欲しくなるEctoの機能(Awesome Rubyより)


infinum.coより

def registration_changeset(struct, params) do
  struct
  |> cast(params, [:email, :password])
  |> validate_required([:email, :password])
  |> unique_constraint(:email)
  |> put_password_hash()
end

def update_profile_changeset(struct, params) do
  struct
  |> cast(params, [:first_name, :last_name, :age])
  |> validate_required([:first_name, :last_name])
end

|>はElixirの「パイプライン演算子」だそうです。


つっつきボイス: 「Ectoって、ElixirのPhoenixフレームワークで使うやつか」「EctoはORMではない、って書いてますね」「この種のフレームワークを業務で使う動機は今のところ見えないなー」

参考: Rails使いがElixirのEctoを勉強した時のまとめ

Railsアプリの災害復旧プラン


engineyard.comより


つっつきボイス: 「disaster recovery planはRailsアプリに限らず重要っすね」「そういえば最近Engine Yardってひと頃ほど見かけない気がしてきた」

あるRails請負開発者の一日(Hacklinesより)

Planet Argonさん(@planetargon)がシェアした投稿


つっつきボイス: 「相当昔ですが、Oracle日本支社では犬飼ってるって話を思い出しました」「今のオフィス事情だと難しそう」

その他Rails小粒記事


つっつきボイス: 「JavaScriptのテスティングフレームワークというとmocha以外にJestもあるのね」
「最後のパーシャルレンダリング記事、でかいデータで素朴にeach回したら確かに遅い↓」

<% @users.each do |user| %>
    <%= render 'erb_partials/post', user: user %>
<% end %>

「こうやってcollection使う方が確実に速いけど、油断するとつい上みたいに書いちゃうことはあるな」「社内でもたまに見ますね」

<%= render partial: 'erb_partials/post', collection: @users, as: :user %>

参考: Railsガイド 3.4 パーシャルを使用する

Ruby trunkより

提案: Hash#transform_keys!recursive: trueオプション(継続)

config = MyAwesomeFormat.load(file); config.transform_keys!(recursive: true, &:to_sym)みたいに書きたいという主旨です。

def transform_keys!(recursive: false, &block)
  # do original transform_keys! here
  values.each do |v|
    if v.respond_to?(:each)
      v.each{|i| i.transform_keys!(recursive: true, &block) if i.respond_to?(:transform_keys!) }
    else v.respond_to?(:transform_keys!)
      v.transform_keys!(recursive: true, &block)
    end
  end if recursive
end

提案: GC速度と少々引き換えにメモリを削減(継続)


#14370より

Aaron Pattersonさんからのissueです。


つっつきボイス: 「最初にレス付けてるnormalpersonさんは昨年のRubyKaigiのあちこちで名前が出てきてた、普通じゃない人」「凄い名前w」

週刊Railsウォッチ(20170922)特集: RubyKaigi 2017セッションを振り返る(1)、Rails 4.2.10.rc1リリースほか

入れ違いで修正

Ruby

Ruby 2.5の陽の当たっていない新機能(Hacklinesより)

行カバレッジやブランチカバレッジ機能などを紹介しています。

Y -> 1: def hello(number)
Y -> 2:   if number == 1
Y -> 3:    'world'
Y -> 4:   else
N -> 5:     'mars'
Y -> 6:   end
Y -> 7: end
Y -> 8:
Y -> 9: hello(1)

つっつきボイス: 「この機能がRubyMineみたいなIDEと連携したらすごくうれしい」「名前忘れたけどこういうカバレッジのgemあった: 絶対に通過しないコードをあぶり出したりとかできる」

なお2.5のカバレッジについては以下でChangelogをざっくり訳してあります。

Ruby 2.5.0リリース!NEWSを読んでみた

Rubyの継承で動的に引数を渡す(RubyFlowより)

# 同記事より
class Render
  def self.engine; end

  def self.inherited(subclass)
    puts subclass        #=> Memo::Render
    puts subclass.engine #=> nil !!!
  end
end

つっつきボイス: 「Rubyってここまでエグいコードも書けるんだなって思いますね」「何でもアタッチできちゃうとか、ここまでくるともうオブジェクト指向言語というよりオブジェクト指向スクリプトみたいw」

Ruby 2.5のFrozenErrorクラス

2.5.0 :001 > NAME = 'Atul'.freeze
 => "Atul"
2.5.0 :002 > NAME << 'Joy'
Traceback (most recent call last):
        2: from /home/atul/.rvm/rubies/ruby-2.5.0/bin/irb:11:in `<main>'
        1: from (irb):2
FrozenError (can't modify frozen String)

つっつきボイス: 「今までRuntimeErrorだったのがFrozenErrorに変わるのはありがたい」「frozen_string_literalが完了するまでの混乱を少しでも軽くするためでもあるんでしょうね」

参考: frozen_string_literalが入って気づいた、メソッド設計の原則

Kernel.method_added


つっつきボイス:Kernel.method_addedなんてのがあるのか: 特定のメソッド追加にフックかける」「実体はModuleにあった↓」

# docs.ruby-lang.org/ja/2.5.0/method/Module/i/method_added.html
class Foo
  def Foo.method_added(name)
    puts "method \"#{name}\" was added"
  end

  def foo
  end
  define_method :bar, instance_method(:foo)
end

=> method "foo" was added
   method "bar" was added

参考: Module#method_added

aruba: CLIアプリをRSpecやMiniTestでテストするgem(Awesome Rubyより)


つっつきボイス: 「あのCucumberがやってるんですね」「バッチ処理のテストをRSpecとかで書けるし、Ruby以外に任意のCLIに使えるのがいいな」


app.cucumber.pro

Rubyのシンボル話その後

#14347はちょうど前回のウォッチで取り上げました。


つっつきボイス: 「あちこちで話題になってるやつですね」「途中まで読んでた」「やっとシンボルは文字列ではないということになったと」

Rubyのシンボルをなくせるか考えてみた(翻訳)

Rubyはやっぱり死んでない(Ruby Weeklyより)

こちらもEngine Yardのブログです。

ネストしたハッシュをlambdaでリファクタリング

同記事より
pub.doc[‘programs’].each &remove_icons(‘grades’, &remove_icons(‘units’))
def remove_icons value_key=nil, &block
  lambda do |key, value|
    next if key == ‘_order’
    value.delete ‘icons’
    if value_key
      value[value_key].each(&block) if block_given?
      value[value_key].each(&remove_icons) unless block_given?
    end
  end
end

つっつきボイス: 「うんうんよくあるやつ:再帰で書きましょう!みたいな」「Hashの再帰って何かと面倒ですよね」「せいぜい3階層ぐらいしか潜らないことをわかって書いてるのに、RuboCopに怒られたりとか」

高木さん

SQL

PostgreSQLにはmeltdownパッチは不要だが少し遅くなる(Postgres Weeklyより)


つっつきボイス: 「meltdownは基本的にカーネルの問題だからアプリにパッチが必要になることはそうないかと」「パッチで遅くなるのはもうしゃーない」「AWSもいろいろ言ってるけど対策すれば遅くなるっしょ」

参考: CPU脆弱性Meltdownのパッチ適用でベンチマークスコアが25%低下した

PostGraphile: PostgreSQLをGraphQL API化するJSライブラリ(Postgres Weeklyより)


graphile.orgより

以下を実行してhttp://localhost:5000/graphiqlをブラウザで開くといきなりGraphiqlが動きました。N+1クエリも克服しているそうです。これ凄いかも。

npm install -g postgraphile
postgraphile -c postgres://user:pass@host/dbname --schema schema_name

PGLogicalがアップデート(Postgres Weeklyより)

PostgreSQLの動作を知る(Postgres Weeklyより)

Pythonのツールを使います。


つっつきボイス: 「お、Internalとか書いてるけどこれはむしろ入門向け記事ですね: 量は多いけど相当やさしい内容」

JavaScript

(a ==1 && a== 2 && a==3)trueにする知見が続々

BPS社内で盛り上がりました。

// Stackoverflowより
var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if(aᅠ==1 && a== 2 &&ᅠa==3) {
    console.log("Why hello there!")
}

ifに半角のハングル文字を使うという荒業を繰り出したり、C++などでやってみたりしています。

JavaScriptの二進木、再帰、末尾呼び出し最適化

nullとundefinedとは

CSS/HTML/フロントエンド

Web Componentsの秘密

参考: MDN Web Components

フロントエンドのエラー表示を再考する


logrocket.comより

blog.jxck.ioの新着記事


つっつきボイス: 「これは読んでおきたい記事」「そういえばこれと少し似た感じの、ネコのアイコンのブログ記事が話題になってましたね」「ネコのアイコン…?」「あったこれ↓」「あこの人か: アイコンとか全然気にしてなかったw」

参考: ソフトウェアの互換性と僕らのUser-Agent文字列問題

Screenshot-to-code-in-Keras: ニューラルネットワークでモック画像から静的HTMLページを生成(GitHub Trendingより)

アニメーションGIFが巨大すぎるのでここには貼りませんでした。


つっつきボイス: 「すげっ」「Bootstrapにも対応してるみたいですね」「一回こっきりの案件とかならかなりイケそう」「HTMLコーダー界に激震走るか」

その他

YAGNIを実践する


dev.to/gonedarkより


つっつきボイス: 「社内にもYAGNIを愛して止まない人がいるから彼を観察してるとだいたいわかりますよ」

参考: Wikipedia-ja YAGNI

Slackにprivate shared channel機能が追加


つっつきボイス: 「これありがたい: shared channelは前からあるけどpublicにしかできなかったんで」

Windows CLIの改善

WSLのchmod/chownの改良とtar/curlの追加です。

なお、こんなのもありました。

DOS窓の|は大丈夫だそうです。

Docker for macで/etc/localtimeがマウントできない問題

minio: Amazon S3 API互換のオブジェクトストレージサーバー


minio.ioより


つっつきボイス: 「S3互換のこういうのは他にもありますけどね」「GCPやAzureとかいろんなクラウドで使えるのはよさそう」「今さらですがオブジェクトストレージサーバーって何でしたっけ?」「AWS S3みたいなサービスがそれです: WebDAVみたいにRESTfulにオブジェクトにアクセスできるサービス」

HighwayHash: Go言語の爆速ハッシュ生成ライブラリ


つっつきボイス: 「10G/secとか確かに超速い」「ハッシュは速度だけあってもいかんので、ちゃんと分散してるかとかも大事ですね」

データがあれば使えるCloud AutoML VisionをGoogleが発表

一般のニュースにもなってますが一応。


つっつきボイス: 「今はデータサイエンティストやAIエンジニアが明らかに不足してるからどこもカスタマイズとかチューニングに手が回らなくて、こうやってそこそこのものを公開して好きに使ってくれ、みたいな方向に向かってる感じですね」「ユーザーに丸投げですか」

参考: Googleが「Cloud AutoML Vision」を発表、独自のデータセットを使ったカスタム機械学習モデルが簡単に構築できるように

番外

暗算術

a% of b = b% of aは初めて知りました。「25の16%=16の25%」みたいに使うそうです。


つっつきボイス: 「計算すると確かにそうなってるな: 式で見ると一瞬でわかるけど言われるまで気づきにくい」「英語圏なんで単位のフィート換算とかいらなさそうなのも多いです」「ひと頃入社試験でよく出されたフェルミ推定なんかやるときは、こういうのを何となくでも知っておかないと手も足も出なかったりしますね」

参考: 暗記しておくとなにかと便利なプチ公式まとめ

これは欲しい


つっつきボイス: 「Nintendo Laboとどっちが子どもにウケるかなと思って」「今ならポプテピピックっしょw」「あれはもう子どもの反応が面白すぎますね」

学習/プログラミング不要の産業ロボット


つっつきボイス: 「荷物の積み下ろしとかまでやってます」「人間雇う方がまだまだ安いな、今のところは」

Switchエミュレータ(GitHub Trendingより)


つっつきボイス: 「ソフトはともかくハードウェアはそうもいかないか」

その後、GPL V2というライセンスの厳しさや、パチンコの当たり判定システムの話題で盛り上がりました。

ルーシーさん

自己修復コンクリート


今週は以上です。

バックナンバー(2017年後半)

週刊Railsウォッチ(20180112)update_attributeが修正、ぼっち演算子`&.`は`Object#try`より高速、今年のRubyカンファレンス情報ほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Rails公式ニュース

Ruby Weekly

Awesome Ruby

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Postgres Weekly

postgres_weekly_banner

Frontend Weekly

frontendweekly_banner_captured

JSer.info

jser.info_logo_captured

Github Trending

160928_1701_Q9dJIU


画像とjQueryだけでグラフにアニメーションをつけてみた

$
0
0


こんにちは、デザイナーのスギヤマです。
この間の雪を降らせるJSが一部で好評だったので、引き続きJSな記事を書いてみます。

サイトを作ってるとグラフなど、インフォグラフィックスを動的に表示して、インタラクティブなコンテンツにしたいといった要望は多々あると思います。
canvas要素を使って表現したりしますが、canvasが使えない(わからない)場合もあると思います。
なので、今回はcanvasを使わずに、画像とjQueryを使って、それっぽい動きのあるグラフを作ってみます。

サンプルコード

いかがでしょう?それっぽく動くグラフになっているかと思います。
今回は、CSSのoverflow:hiddenとjQueryのanimate()関数を利用しています。
以下で簡単に説明していきます。

動きをつける単位で、グラフを画像パーツに分割する

グラフのデザインデータは、psdなどでもらえる場合が多いと思うので、以下のように動きをつける単位で分割していきます。
この時、画像の高さと幅を全て揃えておくことがポイントです。
こうすることで、全ての画像の基準点が同じになるので、動かしやすくなります。
何より、CSSで要素の位置の調整が不要になるので楽に進められます。
(画像のサイズに制限があったりする場合は、この限りではありません。)

HTML/CSSで分割した画像を1つのグラフにする

先ほど分割した画像をHTMLとCSSを用いて1つのグラフにしていきます。
コードはサンプルコードを見て頂くとして、解説が必要なもののみ簡単に説明します。

CSS 21行目

.image-box.graph > span.info-graph2 {
  ...
  height: 2px;
 ...
}

高さを2pxにしています。
これは高さを0pxにした場合は幅も0になる為、
アニメーションする時に横方向へ広がるアニメーションも起こってしまいます。
この防止策として、意図的に高さを少し与えて横幅が維持されるようにしています。

CSS 26行目

.image-box.graph > span.info-graph2 {
  ...
  background-position: left bottom;
}

background-positionを使い、棒グラフを下端に固定します。
こうすることで、高さが変化しても棒グラフの位置は変わらないので
棒グラフが下から伸びるように見せる事ができます。

CSS 28行目以降

.image-box.graph > span.info-graph3 {
  display: block;
  opacity: 0;/*初期状態で非表示にする為、透明度を0にします*/
}
.image-box.graph > span.info-graph4 {
  display: block;
  width: 0px;/*初期状態で非表示にする為、幅を0にします*/
}
.image-box.graph > span.info-graph5 {
  display: block;
  opacity: 0; /*初期状態で非表示にする為、透明度を0にします*/
}

その他、各要素のアニメーションの初期状に合わせて、幅を0pxに設定したり、透明度を0に設定したりします。
また、必要に応じて、z-indexで重ね順を調整すると良いでしょう。

animate()関数を使い、幅や透明度を元に戻す

アニメーションをさせる為のコードを書いて行きます。
ここでもコードはサンプルコードを見て頂くとして、解説が必要なもののみ簡単に説明します。

JS 5行目

$('.info-graph2').animate(
  {'height': '505px'},
  400,
);

400msで棒グラフの画像の表示高を元に戻して、グラフが上に伸びるように表示させています。
グラフの伸びる速度を調整する場合は、animate()関数の第2引数の数値を変更する事で調整できます。

JS 13行目

setTimeout(function(){
  $('.info-graph3').animate(
    //400msでfade-inさせます。
    {opacity:'1'},
    400,
  );
},500);

400msで、2つの王冠マークをフェードインさせます。
また、フェードインのタイミングを細かく調整したかったので、setTimeout()関数を利用しています。
棒グラフの表示アニメーション終了後にフェードインを行いたい場合は、1つ前のanimate()関数の第3引数completeにこの処理を入れる事で対応できます。

JS 21行目

setTimeout(function(){
  $('.info-graph4').animate(
    //600msで矢印の画像の表示幅を元に戻して表示させます。
    {'width': '601px'},
    600,
    function(){
      //吹き出しのフェードイン
      //(ここは終了後に実行したいので、引数completeを使う方向で調整しています)
      $('.info-graph5').animate(
        //800msでfade-inさせます。
        {opacity:'1'},
        800,
      );
    }
  );
},750);

600msで、矢印が左から右へ伸びるアニメーションを行い、
その後、800msで、吹き出しをフェードインさせます。
ここでは、animate()関数の第3引数に吹き出しのフェードイン処理を入れ、一番最後に表示されるようにしています。

まとめ

いかがでしたでしょうか?
今回はoverflowとjQueryのanimate()関数を利用して、グラフをそれっぽく動かしてみました。
応用する事で、他にもユニークなアニメーションの実装ができそうです。
機会があれば、ぜひ試して見てください。

デザインに関するお問い合わせ

キャンペーンサイトやメディアサイト、コーポレートサイト制作など、幅広く対応致します。デザインに関するお問い合わせ、ご相談は下記ページより承っております。お気軽にお問い合わせください。

BPS x DESIGN

URL: https://design.bpsinc.jp

Vue.jsサンプルコード(32)郵便番号に対応する地図を表示する

$
0
0

32. 郵便番号に対応する地図を表示する

  • Vue.jsバージョン: 2.5.2
  • 2つの郵便番号フィールドに郵便番号を入力すると、Enterを押さなくてもGoogleマップを動的に表示します。
  • 1つ目のフィールドに入力するだけでもマップが表示されます。
  • 画面をリロードすると最初の状態に戻ります。

サンプルコード


バックナンバー(Vue.jsサンプルコード)

Vue.jsサンプルコード(01〜03)Hello World・簡単な導入方法・デバッグ・結果の表示とメモ化

週刊Railsウォッチ(20180302)Ruby 2.6.0-preview1とWebpack 4.0リリース、爆速検索APIサービスAlgolia、Clowneでモデルをクローンほか

$
0
0

こんにちは、hachi8833です。Ruby生誕25周年おめでとうございます!

3月最初のウォッチ、いってみましょう。最近社内での記事つっつき会が盛り上がっていて嬉しい限りです。

Rails: 今週の改修

Rails 5.2はまだ動いています。「2月中に出したい」は叶いませんでした。以下は5.2のcommitからです。

紀元前の日付の扱いをPostgreSQLに合わせた

# activemodel/lib/active_model/type/date.rb#L44
         def new_date(year, mon, mday)
-          if year && year != 0
+          unless year.nil? || (year == 0 && mon == 0 && mday == 0)
             ::Date.new(year, mon, mday) rescue nil
           end
         end

つっつきボイス: 「出た〜紀元前!」「どこに使うんだろうか」「は〜ん、年を-4みたいにできるようになったのか↓」

# activerecord/test/cases/adapters/postgresql/date_test.rb#L31
+  def test_bc_date_leap_year
+    date = Time.utc(-4, 2, 29).to_date
+    topic = Topic.create!(last_read: date)
+    assert_equal date, Topic.find(topic.id).last_read
+  end

PostgreSQLのdatedatetime同様に無限値を扱えるようにした

# activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb
+# frozen_string_literal: true
+
+module ActiveRecord
+  module ConnectionAdapters
+    module PostgreSQL
+      module OID # :nodoc:
+        class Date < Type::Date # :nodoc:
+          def cast_value(value)
+            case value
+            when "infinity" then ::Float::INFINITY
+            when "-infinity" then -::Float::INFINITY
+            else
+              super
+            end
+          end
+        end
+      end
+    end
+  end
+end

つっつきボイス: 「どゆこと?」「PostgreSQLって'-infinity'::dateって書き方できるとは」「さすがぽすぐれ: 知らないと使わないだろうけど」「マイナスのInfinityもあると: そりゃそうだ」

# activerecord/test/cases/adapters/postgresql/date_test.rb#L6
+class PostgresqlDateTest < ActiveRecord::PostgreSQLTestCase
+  def test_load_infinity_and_beyond
+    topic = Topic.find_by_sql("SELECT 'infinity'::date AS last_read").first
+    assert topic.last_read.infinite?, "timestamp should be infinite"
+    assert_operator topic.last_read, :>, 0
+
+    topic = Topic.find_by_sql("SELECT '-infinity'::date AS last_read").first
+    assert topic.last_read.infinite?, "timestamp should be infinite"
+    assert_operator topic.last_read, :<, 0
+  end

「そういえばMySQLのdateは確か内部でstringになってて、マイナスすると9999みたいに9並びになっちゃう9999-99-99みたいなありえない日付のクエリでも通ってしまう」「えー」「PostgreSQLは確か型チェックか何かをやってて、9999はまだしも9999-99みたいなのになるとエラーになったと思う」

参考: MySQL 5.6マニュアル 11.3.1 DATE、DATETIME、および TIMESTAMP 型

DATETIME 値の範囲は ‘1000-01-01 00:00:00.000000’ から ‘9999-12-31 23:59:59.999999’ であり

MemCacheとRedisでローカルキャッシュのread_multifetch_multiをサポート

# activesupport/lib/active_support/cache/strategy/local_cache.rb#L123
+          def read_multi_entries(keys, options)
+            return super unless local_cache
+
+            local_entries = local_cache.read_multi_entries(keys, options)
+            missed_keys = keys - local_entries.keys
+
+            if missed_keys.any?
+              local_entries.merge!(super(missed_keys, options))
+            else
+              local_entries
+            end
+          end


つっつきボイス: 「RedisとかMemCacheのAPIで1個ずつアクセスするより一括で取ってくる方が速いんで、そうなるように拡張系の命令を使ったと: まあ書いてあるとおりですねw」

シリアライズのinclude:が効かなくなることがある問題を修正

# activemodel/lib/active_model/serialization.rb#L180
         unless includes.is_a?(Hash)
-          includes = Hash[Array(includes).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
+          includes = Hash[Array(includes).flat_map { |n| n.is_a?(Hash) ? n.to_a : [[n, {}]] }]
         end

つっつきボイス: 「ネステッドなシリアライズかー」「ま、自分はシリアライズでJSONにしちゃうこと多いけど: ハッシュをシリアライズするよりJSONにしとけばRuby以外でも使えるし」「確かにー」

RuboCopの設定でFoo::methodスタイルを禁止に

# .rubocop.yml#L159
+# Prefer Foo.method over Foo::method
+Style/ColonMethodCall:
+  Enabled: true

RuboCopのデフォルト設定ではメソッド呼び出しの::記法は禁止されていないんですね。


つっつきボイス:YAML::loadみたいな記法見たことあったな」「クラスメソッドならこの呼び方も一応できる」

::は、定数(クラスやモジュールも含む)、コンストラクタ(Array()Nokogiri::HTML()など)の参照にのみ使う
Rubyスタイルガイドを読むより


Rubyスタイルガイドを読む: 文法(1)メソッド定義、引数、多重代入

Rails 6ではUTF-8エンコーディングを強制しなくなる

ここからはRails 6のmasterブランチです。

主要なWebサイトのほとんどでTLS 1.0が無効になったことで、IE8以下のブラウザの利用がどんどん困難になった。他のブラウザには影響しないので、UTF-8エンコーディングはデフォルトでは強制しないことにする。
Andrew White


つっつきボイス: 「『これでShift_JISで書けるぜ!』なんてことにはならないだろうけどね」「nilで『強制しない』という意味なのか↓」

# actionview/lib/action_view/railtie.rb#L12
+    config.action_view.default_enforce_utf8 = nil

「昔話になっちゃうけど、ガラケー時代のエンコーディング周りはほんと地獄でしたねー: キャリアに応じてShift_JISで出力したりEUC-JPで出力したりとか、キャリアごとに顔文字変換するとか」「音符がうんこになるバグなんてのもありましたね」「そもそも携帯ネットワークはインターネットでもなければIPネットワークすらないし、RFC準拠とかなかったし」「ひえ〜」「メアドをRFC準拠の正規表現で処理したらユーザーからメアドが通らなくなったって苦情が来たり: 数字で始まったりドット2つ重ね..みたいな非RFCなメアドが使われちゃってた」

参考: 【PHPで作る】初めての携帯サイト構築 – 第3回 携帯サイトの文字コードに気をつける

新機能: "rails routes --expandedを追加

$ rails routes --expanded
--[ Route 1 ]------------------------------------------------------------
Prefix            | high_scores
Verb              | GET
URI               | /high_scores(.:format)
Controller#Action | high_scores#index
--[ Route 2 ]------------------------------------------------------------
Prefix            | new_high_score
Verb              | GET
URI               | /high_scores/new(.:format)
Controller#Action | high_scores#new
--[ Route 3 ]------------------------------------------------------------
Prefix            | blog
Verb              |
URI               | /blog
Controller#Action | Blog::Engine

[ Routes for Blog::Engine ]
--[ Route 1 ]------------------------------------------------------------
Prefix            | cart
Verb              | GET
URI               | /cart(.:format)
Controller#Action | cart#show

こんな感じで出せるそうです。


つっつきボイス: 「これうれしい!」「Route 1はちょいダサかな」「いつものrails routesだとURLがめちゃ長くなりますからね」

新機能: ActiveModel::Attributes#attributesを追加

# activemodel/lib/active_model/attributes.rb#L69
+    def attributes
+      @attributes.to_hash
+    end

つっつきボイス: 「アトリビューツアトリビューツw」「何という名前」「その実態は#to_hashと: ということはattributesはハッシュじゃないのかな」「あと#to_hashすることでオブジェクトが別になるからdupしていることになる」

システムテストのスクショのパスを絶対パスに変更

ログからすぐ開くのに便利だからだそうです。

# actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb#L46
           def image_path
-            @image_path ||= absolute_image_path.relative_path_from(Pathname.pwd).to_s
+            @image_path ||= absolute_image_path.to_s
           end

つっつきボイス: 「こういう変更は地味に大事!マジで」「Pathname.pwdもUnixのシェルコマンドそのまんま」「先週のウォッチで出たrm_rfみたい」

Rails

Rails 5.2.0の良スライド3本立て


つっつきボイス: 「TechRachoでも5.2.0をまとめようと思ってたんですが、重要な部分はこのスライド3本でほぼ網羅されているんで、もういいかなと」「いいと思いまーす」「『ここを見ろ、以上』ということでー」

そのまま一同でしばしスライドつっつきになだれ込みました。

apply_join_dependencyかー」「association scopeのJOINが無視されてた話とか、つらいなー」

Webpack 4.0がリリース


auth0.comより


  • Node JS 4のサポート廃止: 今後はNode >= 8.9.4で
  • さよならCommonsChunkPlugin、こんにちはSplitChunksPlugin
  • WebAssemblyをサポート
  • javascript/autoなどのModule Typeをサポート
  • developmentモードとproductionモードを導入
  • webpack.config.jsでのエントリポイント定義が不要に
  • ビルドの高速化
  • プラグインシステムの刷新
  • コマンド体系がwebpck-cliに全面移行(webpack-cliは今後インストール必須)
  • NoEmitOnErrorsPluginなどの非推奨化

つっつきボイス: 「Node JS 4のサポート廃止は割りと大きいかも: これ系に依存してるのって結構あった気がする」「Zapierは”node.js v4.3.2″ってなってますね」「WebAssemblyもいいな」

書籍『99 Bottles of OOP』


sandimetz.comより

名著『オブジェクト指向設計実践ガイド』でおなじみのSandi Metz女史の新刊が昨年から発売されています。ちょうど翻訳まで終えた記事「Why We Argue: Style — Sandi Metz」の末尾の広告で知りました。セールに気づけばよかった…

同書の章タイトルだけ自分用に雑に訳してみたのですが、場所をとるのでGistに置いてみました。

なお、同書のタイトルは以下ののもじりです。宴会で一気飲みを煽るときに歌うんでしょうか。


つっつきボイス: 「↑ジャケがいい」「酔っぱらいの歌ですな」
「前作『オブジェクト指向設計実践ガイド』は手続き脳が抜けきれてない私にはすごくよかったんで、くやしいけどこの本も定価で買っちゃいました」「ワタイも買ってたのに後忘れてたー」「そういえばJavaのインターフェースって特異だから、あの考え方を前提にするとRubyではどう適用したらいいのか最初よくわかんないかもしれないですね」「それはあるかも」
「たとえばPHPやPerlみたいな世界からRuby脳に変わるときにはオブジェクト指向設計ができてないといろいろ大変っすね: Rubyのライブラリがそもそもオブジェクト指向全開なものが多いし」「とにかくこういう本は一冊読んでおくべき」

参考:

ActiveRecord.no_touchingでtouchingを一時的に無効化する(RubyFlowより)

# 同記事より
user = User.find(user_id)

ActiveRecord::Base.transaction do
  User.no_touching do
    user.photos.find_each do |photo|
      # userはtouchされない
      photo.update!(some_attributes)
    end
  end

  user.touch
end

つっつきボイス: 「む、サイトが開かない」「落ちてるかな?」

今は動いています。

参考: Rails API no_touching

KubernetesデプロイのためにRailsをリファクタリングした(RubyFlowより)

# 同記事より
require 'yaml'
require 'erb'

class ConfigParser
  def self.parse(file, environment)
    YAML.load(ERB.new(IO.read(file)).result)[environment]
  end
end

つっつきボイス: 「Kubernetesはこれだけconfigしないといけない↓のが大変: それぞれenvをどうやって注入するかとか」「そういうコンテナサービスについて気を付けるべき点は例の超定番『The Twelve-Factor App』で言われてますね」

  • ローカルアプリ
  • Dockerコンテナを使うローカルアプリlocal app using docker containers
  • docker-composeを使うローカルアプリ
  • CapistranoをOpenStackとベアメタルサービスにデプロイ
  • Kubernetesからminikubeと実際のクラスタにデプロイ


12factor.netより

そういえばRuby25の会場でtagomorisさんが「クバネーティス」と発音していました。個人的には「クーベルネイテス」の方が何だかドイツ語っぽくてええかなという気もしたり(↓ギリシャ語由来だそうです)。

タイムゾーン関連記事2本(Hacklinesより)

いずれも短い記事です。

# 同記事1より
class ApplicationController
  around_filter :set_time_zone

  private

  def set_time_zone
    old_time_zone = Time.zone
    Time.zone = 'Asia/Kolkata' # current_user.time_zone
    yield
  ensure
    Time.zone = old_time_zone
  end
end
# 同記事2より
  def in_time_zone(zone = ::Time.zone)
    time_zone = ::Time.find_zone! zone
    time = acts_like?(:time) ? self : nil

    if time_zone
      time_with_zone(time, time_zone)
    else
      time || self.to_time
    end
  end

つっつきボイス: 「うんうん、マルチロケールのアプリでこういうのやるよね: 弊社のManga Rebornでもそういうコード書いた覚えある」「記事1はTime.zone使ってて、記事2はTime.zone使うなin_time_zone使えって言ってるー」
Time.zoneってクラス変数だった気がするんだけど、マルチスレッドだと死ぬやつじゃね?」「それともconfigか? configならスレッドスコープ」「あ、両方か↓: config.time_zoneTime.zone」「一時的に使うだけならTime.zoneでやるなということね」

# http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.htmlより
# application.rb:
class Application < Rails::Application
  config.time_zone = 'Eastern Time (US & Canada)'
end

Time.zone      # => #<ActiveSupport::TimeZone:0x514834...>
Time.zone.name # => "Eastern Time (US & Canada)"
Time.zone.now  # => Sun, 18 May 2008 14:30:44 EDT -04:00

参考: Rails API in_time_zone

外部キーなしでActiveRecord関連付けを行う(RubyFlowより)

# 同記事より
def find_target
  # ... あまり関係ないコードも混じってるのであしからず ...
  sc = reflection.association_scope_cache(conn, owner) do
    StatementCache.create(conn) { |params|
      as = AssociationScope.create { params.bind }
      target_scope.merge(as.scope(self, conn)).limit(1)
    }
end

つっつきボイス:unscope?」「unscopeは割りと使いますよ: デフォルトスコープのorderだけ消したりとか」「今はreorderとかあるけど」

Railsのdefault_scopeは使うな、絶対(翻訳)

rails_refactor: 名前リファクタリングgem(Hacklinesより)


つっつきボイス: 「あー、これコントローラ名をリファクタリングするgemか」「IDEの機能一発でできるかなと思ったんですが」「できないんですねこれが: routes.rbとかビューとかあちこちリネームが必要になるんで地味ーに面倒」「ときどき直し漏れがあったり: でgit commitするときに気づくと」
「まーGemfileに追加してまでやることかな?とは思う」「使い捨てですかね」「でもこの機能がRailsに標準で取り込まれるならわかるし、あったらすごくうれしい: rails generate renameみたいな感じで」「あー!それ欲しいかもー!」

searchkick: ElasticsearchとRailsでインテリジェント検索(Awesome Rubyより)

ankaneさん作のgemです。

# 同リポジトリより

# モデル
class Product < ApplicationRecord
  searchkick
end

# インデックスにデータを追加
Product.reindex

# クエリ
products = Product.search("apples", fields: [:name])
products.each do |product|
  puts product.name
end

つっつきボイス: 「へーこういう曖昧マッチが効くのか↓」「Elasticsearchのハンドラ的なやつっぽいけど、結構何でもできそうに見える」

  • stemming – tomatoes matches tomato
  • special characters – jalapeno matches jalapeño
  • extra whitespace – dishwasher matches dish washer
  • misspellings – zuchini matches zucchini
  • custom synonyms – qtip matches cotton swab

Clowne: Rubyモデルをクローンするgem(RubyFlowより)


同記事より

# 同記事より
# app/cloners/order_cloner.rb
class OrderCloner < Clowne::Cloner
  include_association :additional_items
  include_association :order_items, scope: :available

  nullify :payed_at, :delivered_at

  finalize do |source, record, _params|
    record.promotion_id = nil if source.promotion&.expired?
    record.uuid = Order.generate_uuid
    record.total_cents = OrderCalculator.call(record)
  end
end

Clowneは「道化師」(clown)とクローンを掛けてますね。


つっつきボイス: 「ははぁ、上の図↑みたいにリレーションがチェインしているものをクリーンにクローンできるってことか」「たとえばユーザーアカウントの項目が複数のテーブルでできていて、そのアカウントをコピーしたい、でもリレーション先のマスターはコピーしたくない、とか」
「ところで他人が書いたdupとかって基本信じたくないというのは、ありますね」「あーたしかにdeep copy的なのは自分で実装しないとコワイw」「ActiveRecordに特化してそういう部分をやりやすくしてくれるgemなんでしょう、きっと」「複雑なやつをいっぱいクローンしないといけないときとかにはありがたいかも」
「そういえばJavaだとこの手のcloneメソッドは自分で書かないといけないようになってましたね」「ですですー」

Overcommitで各種静的解析を呼んでRubyコードをlintする(Random Rubyより)

Git Hookを使ってコミット時のフックでRuboCopなどを呼んでいます。



同リポジトリより


つっつきボイス: 「社内で使ってた人がいたのを思い出したので」「Overcommitは、基本入れた方がいいですね: CIが重いとつらくなるけど」「CIがfailしたらgit commitが失敗するようにしたり」「それはスバラシイ!」

「お、ちょうどいいところに: Overcommitって使ってみてどうでした?」「…みんなが使ってくれないと意味がないかも…」「あ確かに」「CI側をがっつり設定して、CIがfailするものをプッシュするとすごく怒られるようにしておけば、みんな自主的にこういうツールを使うようになるかも」

VAPIDでプッシュ通知

# 同記事より
# app.rb
post '/push' do
  Webpush.payload_send(
    message: params[:message]
    endpoint: params[:subscription][:endpoint],
    p256dh: params[:subscription][:keys][:p256dh],
    auth: params[:subscription][:keys][:auth],
    ttl: 24 * 60 * 60,
    vapid: {
      subject: 'mailto:sender@example.com',
      public_key: ENV['VAPID_PUBLIC_KEY'],
      private_key: ENV['VAPID_PRIVATE_KEY']
    }
  )
end

正直、プッシュ通知しようとするサイトはむかつきますが。


つっつきボイス: 「RFCだから一応標準」「Webプッシュで認証/承認周りをやってくれるのね」

参考: Web PushをFCMとVAPIDで認証してブラウザにプッシュ通知を送る

Proxyパターンを再考する(Hacklinesより)

#prependをProxyパターンで使うお話です。

# 同記事より
module Proxy
  require 'etc'

  # Dynamically re-creates receiver's class methods, intercepts calls 
  # to them and checks user before invoking parent code
  #
  # @param receiver the class or module which has prepended this module
  def self.prepended(receiver)
    obj_mthds = receiver.instance_methods - receiver.superclass.instance_methods
    obj_mthds.each do |m|
      args = receiver.instance_method(m).parameters # => [[:req, :x], [:req, :y]]
      args.map! {|x| x[1].to_s}
      Proxy.class_eval do
        define_method(m) do |*args|
          puts "*** intercepting method: #{m}, args: #{args}"
          raise "Unauthorised access!" unless Etc.getlogin == 'fred'
          super(*args)
        end
      end
    end
  end
end #module

class Account
  def initialize(balance)
    @balance = balance
  end

  def deposit(amount)
    @balance += amount
  end

  def withdraw(amount)
    @balance -= amount
  end

  def self.interest_rate_for(a_balance)
    a_balance > 10_000 ? '3.2%' : '5.5%'
  end

  prepend Proxy

end

つっつきボイス: 「変更前のコードの方、method_missingしてsendするとかエグいなー: Javaのひとたちはこういうコードが許せなかったりとか?」「いやーそこまではないかもー」「これは認証のプロキシなのか!: 権限がなければraiseして、権限があればデレゲートすると」「天才か?」

「で変更後のコード↑はというと、method_missingを使わずに、prependの中でclass_evaldefine_method使ってやると」「たしかにー: 変更後の方がキレイ」「とってもProxyパターン」

Ruby: Proxyパターンの解説(翻訳)

参考: Module#prepend

CertBot: Let’s Encrypt証明書をもっと楽に導入


certbot.eff.orgより: (CC BY

Let’s Encrypt Ruby on Rails and Nginxにしようかと思ってたのですが、記事中のこちらの方が気になったので。

主催しているEFFはElectronic Frontier Foundationという非営利団体です。


つっつきボイス: 「CertBotは有名っすね」「あれLet’s Encrpyptコマンドじゃなかったっけ?」「↓名前変わってた…」「ややこしいのう」

Certbot クライアント(旧・Let’s Encrypt クライアント)
https://letsencrypt.jp/command/より

「まあapt-cache searchしてあるほうを入れればいいかとw」「多分ディストリビューション公式のパッケージ使う方がそのdistroのapache/nginxバージョンにちゃんと合わせられてるので、機能的な問題とかない限り、無理に最新にこだわらない方が安心して使えそう」

参考: Wikipedia-ja 電子フロンティア財団

dev.toは記事のソーシャル画像をこうやって自動生成している(Hacklinesより)

# 同記事より
  def enter_urls
    urls.each do |url|
      3.times do
        enter_url_and_click_preview("#{url}?#{rand(10_000)}=#{rand(10_000)}")
      end
      enter_url_and_click_preview(url)
    end
  end

つっつきボイス: 「SEO業界では記事の冒頭に何か大きい画像を置いとくとアクセスが増えるみたいなのがありますね」「神社の御札みたいなw」「まあ確かにそういう画像があると視線の取っ掛かりにはなりそう」「文字ばっかりの記事よりはデザイン的な座りもよくなりそう」「randかよ↑」

Railsコンソールで便利なヘルパーメソッドを書く(RubyFlowより)

オーストラリアの方だそうです。

# 同記事より
module ConsoleMethods
  def load_jwt!
    token = ::JWT.create(user_id: 'USER_ID_HERE')
    RequestLocals.store[:jwt] = token.jwt
  end
end

つっつきボイス: 「へーConsoleMethodsを拡張するとRailsコンソールでいろいろやれるのか」「Railsコンソールをそこまで使うかどうかというのはあるけど、利用頻度が高いならこうやってヘルパー化しとくといいかも」

RailsをOracleに接続する手順(RubyFlowより)

# 同記事より
default: &default
  adapter: oracle_enhanced
  database: "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=localhost)(Port=1521))(CONNECT_DATA=(SID=xe)))"

development:
  <<: *default
  username: "myproject_development"
  password: "myproject_development"

test:
  <<: *default
  username: "myproject_test"
  password: "myproject_test"

production:
  <<: *default
  username: "myproject_production"
  password: "myproject_production"

oracle-enhancedアダプタはdb:createができないらしいので、SQLで直接やってます。


つっつきボイス: 「来たなオラクル: 考えたくないヤツ」

Algolia: 爆速リアルタイム検索APIサービス


algolia.comより

とりあえずデモはめちゃめちゃ速いです。


つっつきボイス: 「そのデモサイトで適当に映画のタイトルぶっこんでみてください」「おーマジ速いわこれ!」「しかもUIカッケ~」「日本語だとあんまり引っかからないかも?」
「こういうの真面目にスクラッチから設計して作ってこれだけの性能出すのはタイヘン」「Solrとかカテゴリテーブルとか色々設計してSQLをWHERE文なりGROUP文なりを地獄のような感じで設計するとか」「有料だけどこれ使って速くなるなら使ってもええんじゃね?と思う」「裏でElasticsearchとか動いてるのかな」


algolia.com/productより

redis-cell: Redisをコマンドラインで使える

とりあえず手元で動かしてみました。Java 8入れろと言われました。


つっつきボイス:libredis_cell.soを読み出せる」「soできる!」「これで差し替えっぽく使えるという感じかな」

MonitでRailsアプリを監視(RubyFlowより)


mmonit.com/monit/より

監視というとやっぱりイヌですね。


つっつきボイス: 「Monitなつかし!」「こないだSupervisor入れようと思ってたら環境古すぎてMonit入れたけどなっ」「Monitは定番、かつめちゃ歴史長い: いわゆるスーパバイザです」「↓こういう処理とか、やりたいと思うことはひととおり書ける優秀なヤツ」「configはところどころよくわからないけど」

<!-- 同記事より -->
<!-- config/deploy/shared/monit.erb -->

check host <%= fetch(:website_url) %> with address <%= fetch(:website_url) %>
  if failed
    icmp type echo count 5 with timeout 15 seconds
  then alert

Datadog: サイト監視・分析サービス


datadoghq.comより

いっぬがカワイイ。無料プランで5ホストまでできるそうです。


つっつきボイス: 「こっちの方がかわいかったので」「そこ!?」「このDatadogも定番: サービス監視/サイト監視にも使うし、メトリック監視にも使われる」

DHHとconcerns


同ツイートより


つっつきボイス: 「おー大量のconcern!」「mixinがとてもうまく書かれているとこうなる」「しかし1回しか使わないようなmixinをconcernにするかと言われると…」「ですねー」

「これ厳密にはconcernじゃないかもしれないっす: Basecampのディレクトリ構成って、app/の下にこの場合はRecordings(複数形)というディレクトリを作って、その下にこういうのが置かれていました」「へー!」「concernで置きまくるんじゃなくて、あるモデルの配下にそれ用のモジュールを入れるみたいなフォルダ構成なんで、それならまあありかなとは思いました」「あーモデル名と同じフォルダが別にあってその下に置くと: それならワカル」「つまりincludeだけどあちこちで使われるんじゃなくて、あくまでそのモデル用のmixinか」「RuboCopの『1クラス100行制限』で怒られないためにはこのぐらい分散しないと」「例のYouTubeの番組を見た限りではそんな感じでした」

「このincludeは互いの依存関係のあるなしに応じてきれいに書き分けられているからとても読みやすい」「ここまでキレイに書けたらスゴイ」「includeは順序変わると動かなくなったりしがちですね」

「ところでDHHのYouTubeチャンネル、早くも一週間ほど更新止まってますね(´・ω・`)」「こりゃスターウォーズばりに次回作待たされるかもww」

「あとDHHの書き方なんですが、もう全然TDDじゃないんですよ」「お?」「モデルのテストすら全部振る舞いベースで、unitテストを書かないんで、スゲーなーと」「まあ確かに、自分で書いていない、提供されている機能だけを使うんだったらそのunitテストってなくてもいいんじゃね?って思ったりするな」「おー」「実はunitテスト懐疑派w」「たとえばモデルをcreateするテストとかは必要だと思うけど、そういうのはunitテストというより機能テストに近いんじゃないかな」

ここまでいける


つっつきボイス: 「ちゃんとメンテするとこのぐらいになるという見本」「しかし20msecはスゴイ」

Ruby trunkより

Ripperの新機能使いたいからnobuさんのパッチ使わせて

irbでRipperを使いたいそうです。確か今のirbはRubyで書かれた独自パーサーでした。

参考: Rubyリファレンスマニュアル Ripper

Ruby 2.5.0の「逆順バックトレース」がつらすぎる

「Railsで開発していると、バックトレースが逆順だったりそうでないところがあって頭がおかしくなりそう」だそうです。


つっつきボイス: 「何で逆順にしたんだろね?」「コールスタックを登っていく感じを出したかったとか?」

Ruby

Ruby 2.6.0-preview1がリリース(Ruby公式ニュースより)

Ruby生誕25周年のその日にリリースされました。

  • JITで--jit-verbose=1を指定可能に
  • 以下を含む新メソッド
  • パフォーマンス向上
    • Proc#call
    • block.callにブロックパラメータを渡した場合
    • ブロック渡しのパフォーマンス全般
  • その他
    • $SAFEがプロセスグローバルになり、0を渡せるようになった
    • ERB.newの引数の変更

とりあえず記念写真。

SinatraベースのSmashingとWordPressとGitHubウィジェットでイケてるダッシュボードを作った(RubyFlowより)


同記事より

マジックコメント一発でRubyを最適化

# 同記事より
# frozen_string_literal: true

HASH = {
  "mike": 123
}

def getmike
  HASH["mike"]
end

コメント欄で「細かい点がちょっと違うんじゃ?」とツッコまれています。


つっつきボイス: 「Ruby 2.2以降はハッシュキーのstring literalを自動でfreezeするのかー」「String.newと空文字""リテラルのエンコーディングは違う…だと?↓String.new使わんけどなっ」「本編記事よりコメントの方が情報量多いかも」

String.new.encoding
# => #<encoding:ascii-8bit>

"".encoding
# => #<encoding:utf-8>

オブジェクト指向だと何ができるようになるか(Ruby Weeklyより)

これもSandi Metz女史のがっつり読み物系記事です。

Rack::Proxyクイックツアー(RubyFlowより)

Rack Proxyはちょっと工夫するといい感じになるそうです。

  • サブドメインベースで複数のアプリにパススルーする
  • 移動したページへの面倒なリダイレクトルールを扱うのに便利
  • 単一のAPIリクエストを複数のコンカレントバックエンドリクエストに分散し、結果をマージする
  • 認証/承認をリクエストのプロキシより優先的に内部の信頼できるバックエンドに無条件に投げる
  • あるドメインから別のバックエンドへのプロキシでCORSが複雑になるのを回避する

つっつきボイス: 「Rackミドルウェアでやるのは確かに簡単で便利: 裏を返すと、Nginxの設定がいかに死ぬほど特殊で面倒くさいかということでもある」「それこそApacheのmod_rewriteに匹敵する地獄感を味わえますよ」「sendmail.cfとか」

参考: Mozilla オリジン間リソース共有 (CORS)

Rubyで文字列の重複を削減する(Ruby Weeklyより)

# 同記事より
if ENV['RAILS_ENV'] != "production"
  exec "RAILS_ENV=production ruby #{__FILE__}"
end

require 'memory_profiler'

MemoryProfiler.report do
  # this assumes file lives in /scripts directory, adjust to taste...
  require File.expand_path("../../config/environment", __FILE__)

  # we have to warm up the rails router
  Rails.application.routes.recognize_path('abc') rescue nil

  # load up the yaml for the localization bits, in master process
  I18n.t(:posts)

  # load up all models so AR warms up internal caches
  (ActiveRecord::Base.connection.tables - %w[schema_migrations versions]).each do |table|
    table.classify.constantize.first rescue nil
  end
end.pretty_print

frozen_string_literal: true以外にもいくつかRubyの文字列のdupを防ぐ方法を試しています。


つっつきボイス: 「この+""って書き方↓いつも不思議でしょうがないんだよなー」「どうしてこうなった感」

buffer = String.new
buffer.encoding => Encoding::ASCII-8BIT

# vs 

# String @+ is new in Ruby 2.3 and up it allows you to unfreeze
buffer = +""
buffer.encoding => Encoding::UTF-8

Rubyで分散ファイル同期(Hacklinesより)

Ruby Distributed File SyncでRDFSだそうです。Dropbox的なことをやってみたかったとのこと。


つっつきボイス: 「こういうDropbox的なオープンソースソフトウェアありましたねー: 名前思い出せん…SynkThinkだ!」「割りとよくできますこれ: Win/Linuxでも使えるし」


syncthink.comより

Ruby 3のGuildとは


engineering.universe.comより


つっつきボイス: 「Guildの情報が意外に少なかったんですが、これが割りとまとまってるっぽかったので」

TruffleRuby Native

メモリ喰いの代わりに爆速で知られるTruffleRubyのネイティブオプションについての記事です。

MJIT記事の極めつけ

英語版まで出してます。


つっつきボイス: 「この間の翻訳記事↓より後に公開されててちょっとほっとしました」「MJITの作者自らの記事には勝てないっしょw」

Rubyの新しいJIT「MJIT」で早速遊んでみた(翻訳)

こういう書き方


つっつきボイス: 「『とかしてなくて』w」「自分もアクセサでやっちゃったりするかなー: メンバ生で触るの怖いときとか」「まあ隠しとけと言えばそれまでだけど」

MatzのRuby25スピーチまとめ

RubyKaigi 2018 CFP締め切りの駆け込みっぷりが凄い

サイト: RubyKaigi 2018 closed


rubykaigi.orgより

あるあるとはいえ、笑ってしまいました。

SQL

「Why upgrade PostgreSQL?」: PostgreSQLのバージョンごとの修正点を比較表示できるサイト(Postgres Weeklyより)


つっつきボイス: 「これ便利かも」「出力はベタベタっすね…」「読みづらっ!」

(動画)KubernetesネイティブのPostgreSQL(Postgres Weeklyより)

AWS RDSがPostgreSQL 10をサポート(Postgres Weeklyより)

JavaScript

2018年のJavaScript事情(JavaScript Weeklyより)

Reduxの原則に沿ってReact.jsでフロントエンド検索ウィジェットを作る

Mozillaのsource/mapのパフォーマンスをRustとWebAssemblyで改善(JSer.infoより)

TypeScript Deep Dive


つっつきボイス: 「ところで音声読み上げってヒアリングの練習にいいかも」「最近の英語音声読み上げのクオリティ凄いし」

Vue.jsとVuexで自動セーブする

CSS/HTML/フロントエンド

簡単なものはいずれ面倒になる(Frontend Weeklyより)

制約条件の理論をソフトウェアに適用する(Frontend Weeklyより)

「制約条件の理論」は経営寄りの概念だそうです。

参考: Wikipedia-ja 制約条件の理論

Utility-First CSSと関数型的CSS(Frontend Weeklyより)

Go言語だけでフロントエンドする

先週取り上げたhttps://easydatawarehousing.github.io/ferro/にも通じそう(こちらはRubyとOpalとFerro)。

30 Seconds of CSS: CSSスニペットあんちょこサイト(GitHub Trendingより)


atomiks.github.ioより

スニペット数はまだそんなにありませんが、1週間ほどで★1600超えです。


つっつきボイス: 「お、これも中々便利じゃん?」「この間の30 seconds of codeに通じますね」

Elm言語: ブラウザベースの純粋関数型言語

Sandi Metz氏が最近関心を持っているとのことなので。

その他

SourceTree Windows版が更新

Diesel: Rust向けORM & クエリビルダ


つっつきボイス: 「社内のRust勢向けにと思って」

Dockerイメージを小さくする3つのコツ

どのネットワークかと思ったら

開発手法

ITエンジニアには筋トレが大事

私も痛感しています。


つっつきボイス: 「筋肉といえば、ウェブ魚拓の人の筋肉の付き方はヤバイですね」「ガチムチやん…」

コアもOSもシェルも自作

Six Degrees of Wikipedia: Wikipediaの記事のつながりをグラフ表示

よくあるやつといえばそれまでですが、めちゃめちゃ速いです。

Hemingway.app: 英文添削できるエディタ

ちなみに世にあるエッセイ向け英文スタイルガイドの種本のほとんどは、The Elements of Styleだったりします。もう100年近いロングセラー。

論文だとChicago Manual of Styleが定番だったかな。

「パスワード無期限」が推奨に

番外

GitHub issueをブログ代わりに

内容よりそっちがびっくりだったので。

prompts: JSらしいプロンプト(GitHub Trendingより)

AppleのクラウドはGoogleのクラウド

参考: Appleのクラウドサービス「iCloud」がGoogleのクラウドを利用していることが正式に明らかに - GIGAZINE

敷き詰め問題

これ系の問題はたいてい難問になってる気がします。


つっつきボイス: 「先に進むとヤケクソで並べたようなヤツとかあるなー確かに」「草」「この直感に反する感がタマラン」

「プログラマーの誓い」


私はプログラマーとして、以下の誓約を果たすことを誓います。
* 仕事を誠実に、かつ倫理から逸れぬよう進めます。人々に害をなすいかなる要求にも従いません。
* 先人の学びを尊重し、かつ自分の学びを後進と共有します。
* プログラミングは技術であると同時に、科学であり、思いやりであり、共感でもあることを決して忘れず、精巧なアルゴリズムや技術上の論争よりも理解することの方が重要であることも決して忘れません。
* 「わかりません」と言うことを恥じず、困ったときは的確に支援を求めます。
* ユーザーのプライバシーを尊重します。プライバシー情報は、たとえ世界中が知っていようとも自分には明かされないものだからです。
* あらゆる物事を生涯にわたって注意深く扱い、自分がミスをする可能性があることを謙虚に認めます。
* コンピュータのためでなく、人間のためにコードを書くことを決して忘れません。
* 自分の書くコードや自分の行いがもたらす可能性のある結果について常に配慮し、社会的な問題と技術上の問題、どちらの困難についても一方を軽んずることなく同様に尊重いたします。
* 誇りを失わず、勤勉に仕事に専念いたします。
* 自分がいつかは間違いを犯し、間違ったまま進む可能性があることを認めます。先入観に囚われず、誰の言葉であっても決して侮ることなく尊重し注意深く耳を傾けます。
同リポジトリより: 大意

ひと目で分かるヒポクラテスの誓いのもじりですね。

蛇腹型ロボット

Snake-inspired robot uses kirigami to move | Harvard John A. Paulson School of Engineering and Applied Sciences

マヤ文字作文


つっつきボイス: 「そういえば最近カクヨムで読んでるこの小説↓、解説も凝ってて言語マニアにはたまらんかもですヨ」


今週は以上です。

バックナンバー(2018年度)

週刊Railsウォッチ(20180223)Ruby25開催、Rails6のパラレルテスト、書籍「RSpecによるRailsテスト入門」更新ほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

Awesome Ruby

Random Ruby

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Postgres Weekly

postgres_weekly_banner

Frontend Weekly

frontendweekly_banner_captured

JavaScript Weekly

javascriptweekly_logo_captured

JavaScript Live

jslive_logo_captured

JSer.info

jser.info_logo_captured

Github Trending

160928_1701_Q9dJIU

Vue.jsサンプルコード(33)Vue.jsでAJAXを使う

$
0
0

33. Vue.jsでAJAXを使う

  • Vue.jsバージョン: 2.5.2
    • axiosライブラリを併用しています。
  • [Yes or No]ボタンをクリックすると、yesno.wtfの画像をランダムに表示します。
  • 画面をリロードすると元に戻ります。

サンプルコード

ポイント

axiosライブラリを使うとAJAXのやりとりを以下のように簡単に書けるので便利です。

  const vm = new Vue({
    el: "#app",
    data: {b: null},
    methods: {
      a: function() {
        axios.get("https://yesno.wtf/api").then((r) => {this.b = r.data.image})
      },
    },
  })

バックナンバー(Vue.jsサンプルコード)

Vue.jsサンプルコード(01〜03)Hello World・簡単な導入方法・デバッグ・結果の表示とメモ化

JavaScript: Reduxが必要なとき/不要なとき(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

図は英語記事からの引用です。

JavaScript: Reduxが必要なとき/不要なとき(翻訳)

Reduxが登場するまで、複雑なタスクを組むときのステート管理は相当つらい作業でした。Reduxは、Fluxアプリのデザインパターンにヒントを得て、JavaScriptアプリでステートを管理するために設計されました。ReduxはReactと併用されることが多いのですが、ReduxはjQueryやAngular、Vueといった別のフレームワークと併用することもできます。

Reduxのサイズは非常に小さい(依存関係も含めてわずか2KB)にもかかわらず、アプリの各コンポーネントが自分のステートに直接アクセスできるようになります。このとき、子コンポーネントにpropsを送信する必要も、親コンポーネントがデータを受け取るのにコールバック関数を使う必要もありません。

本記事では、Reduxがいかに関数型プログラミングに深く根ざしているか、そしてアプリにReduxを導入するかどうかをうまく決定する方法について解説したいと思います。

「どうしてReduxが必要なの?」

ピカピカの新しいツールが登場するたびにいきなり飛びついてプロジェクトに導入したりしないのは今や常識です。結局のところ、あらゆるコンポーネントはステートを持たないのでしょうか?ステート管理ツールがなぜ今必要なのでしょうか?

誤解しないでいただきたいのですが、Reactはそれだけで十分素晴らしいフレームワークであり、他に何もなくても完全なアプリを書けます。しかしアプリが複雑になるに連れてコンポーネントの数も増加し、フレームワークだけでこれらを管理するのは非常に難しくなることがあります。

そこに登場するのが複雑なアプリを扱いやすくしてくれるReduxです。Reactを少し使ってみれば、Reactのデータフローが親コンポーネントから子コンポーネントにpropsを渡す作業であることがわかります。多数のコンポーネント間をステートやpropsを介してデータフローが行き交う巨大なアプリ内ではコミュニケーションエラーが発生しやすくなり、(信じていただきたいのですが)コードはいつしか読むのも改善するのも非常に困難になってしまいます。

理解のため、以下の図をご覧ください。

https://css-tricks.com/learning-react-redux/より

Reactでは(他のフレームワークでも同様ですが)、親子関係にないコンポーネント同士でのやりとりがスムーズにできません。Reactでは、それが必要な場合にはFluxのパターンに従ってグローバルなイベントシステムを構築せよとアドバイスしています。そしてここがReduxの出番なのです。

Reduxを使うと、アプリの全ステートを保持する「ストア」が使えるようになります。コンポーネントAでステートが変更されるとそのことがストアに伝わります。コンポーネントAのステート変更を監視する必要のあるコンポーネントBやCはストアにサブスクライブすることで、ステートの変更がストアから中継されます。

https://css-tricks.com/learning-react-redux/より

おわかりいただけましたでしょうか?思ったよりずっとよくできています。コンポーネント間のコミュニケーションを元のまま放置していれば、やがてエラーが生じやすくなり、とても読んでいられないコードベースになってしまいます。Reduxはこの流れを変えます。

コンポーネントAが自分のステート変更をストアに送信すると、コンポーネントBとCがステートの変更を必要としている場合に、BとCがストアからAの変更を取得できます。このようにして、データフローのロジックがシームレスになります。

Reduxの本来の目的以外にも、Reduxを使うメリットはたくさんあります。中でも私が重要だと思う部分をリストアップします。

  • 1. 結果が予測可能

「信頼できる情報源」(ストア)が1つだけに限定されているので、アクションとアプリの他の部分の現在のステートを同期するときに伴う問題がほとんど生じません。

  • 2. メンテナンスしやすい

Reduxではコードの編成方法に関する厳密なガイドラインが定められています。これにより結果がさらに予測しやすくなり、コードのメンテナンスも容易になります。

  • 3. テストしやすい

Reduxで書くコードは、テストしやすいコードを書くための黄金律に従って分離される純粋な関数になります。1つの作業だけを行う、独立した小さい関数を書くようにしましょう。

「Reduxって結局要らなくね?」

この点については既に自明な方もいらっしゃるかと思いますが、一応説明いたします。Reduxを無理して使う必要はありません。Reduxを使わない理由が今ひとつ見えにくいこともあるかもしれませんが、以下のいずれかに該当する場合は、おそらくReduxは完全に不要でしょう。

  • 自分や友だち(そこまで仲良くなければ同僚でも構いませんが)が、コンポーネント間でステートの共有や加工を行う方法を既に定義済みの場合
  • Reactなどのフレームワーク経験がまだ浅い場合
  • アプリのほとんどの動作で今後もシンプルなUI変更などぐらいしか行う予定がなく、Reduxストアに含める必要もなければコンポーネントレベルで扱えるようにする必要もない場合
  • サーバーサイドイベント(SSE)やWebSocketsを管理する必要がない場合
  • ビューごとにフェッチするデータソースが1つしかない場合

Reduxの構成要素

初心者がやり方に戸惑わないよう、Reduxのライブラリは2KBに抑えられ、ツール自体が「アクション」「ストア」「レデューサー」の3つのパーツで構成されています。

https://stackoverflow.com/questions/45416237/axios-calls-in-actions-reduxより

アクション(action)

アクションは、関数を用いて生成されるシンプルなイベントであり、アプリのデータをストアに送信します。データの送信は、フォームの送信/API呼び出し/基本ユーザー操作などさまざまな方法で行われる可能性があります。Reduxのあらゆるアクションにはtypeプロパティがあり、アクションの種類(type)や、ストアに送信される情報の「ペイロード」を記述します。アクションの最も基本的な動作例をご覧ください(Gist)。

// The action
  {
    type: ADD_USER,
    payload: {
      username: ‘Chris’,
      email: ‘redux @frameworks.io’
    }
  }
  // The function that creates the action
  function signUpUser(data) {
    return {
      type: ADD_USER,
      payload: data
    }
  }

Reduxには、アプリのどこからでもアクションを呼べるようにするためのdispatch()メソッドが用意されています。このメソッドはアクションをReduxストアに送信してステートの変更を表明します(Gist)。

dispatch(signUpUser(data));

レデューサー(reducer)

Reduxではステートをアプリから(直接)変更できないようになっています。変更にはdispatch()を使います。dispatch()はステート変更の意図を表明するだけのメソッドであり、実際の変更はReduxが行います。

レデューサーは関数であり、ディスパッチされたアクションを介してアプリの現在のステートを受け取り、新しいステートを返します。以下のレデューサーが、現在のステートとアクションを引数として受け取り、次のステートを返す様子をご覧ください(Gist)。

function handleAuth(state, action) {
    return _.assign({}, state, {
      auth: action.payload
    });
  }

もっと複雑なアプリを作る場合は、ReduxのcombineReducers()メソッドの利用が推奨されます。このメソッドはアプリ内のすべてのレデューサーを1つのレデューサーリストにまとめます。あらゆるレデューサーはここでアプリのステートのパーツを扱い、ステートのパラメータはレデューサーごとに異なります(Gist)。

const indexReducer = combineReducers({
    signUp: signUp,
    editProfile: editProfile,
    makePayment: makePayment
  });

ここでもうひとつ注意しておきたいのは、レデューサーは「純粋関数」で記述すべきであるという点です。純粋関数の特徴を以下にリストアップしました。

  • 外部ネットワークや外部データベースへの呼び出しを行わないこと
  • 戻り値は、パラメータの値だけに応じて変わること
  • 引数はイミュータブルに見えること(変更されるべきでないという意味)

ストア(store)

ストアはReduxの心臓部に相当します。ストアは、アプリのあらゆるステートを保持する単一の信頼できる情報源であり、ステートへのアクセス/アクションのディスパッチ/リスナーの登録を行うメソッドを提供します。ディスパッチされたアクションは、レデューサを介して常に新しいステートをストアに保存します。Reduxストアの基本的な例をご覧ください(Gist)。

import { createStore } from‘ redux’;
  let store = createStore(indexReducer);
  let signUpInfo = {
    username: ‘Chris’,
    email: ‘redux @frameworks.io’
  };
  store.dispatch(signUpUser(signUpInfo)); 

関数型プログラミングとRedux

Reduxを使うことになったら、関数型プログラミングの動作について知っておくべきです。Reduxは関数型プログラミングの原理に基づいて作られているので、関数型プログラミングのコンセプトを理解しておくことでReduxがどのようにして操作を行っているかについて洞察を得ることができます。

関数型プログラミングの重要な点についてガイドラインをざっと追ってみましょう。

  • 純粋で、再帰可能で、高階のクロージャや無名関数を利用できる
  • map/filter/reduceなどのヘルパー関数を利用できる
  • 関数は互いにチェイン(連鎖)できる
  • 関数は第一級オブジェクトとして扱える
  • 関数は引数として渡せる
  • 関数/再帰/配列を用いてフローを制御できる
  • 関数のステートは変更されない(イミュータブル)
  • コードの実行順序は重要ではない

関数型プログラミングでは、シンプルで小さく分離された関数を書きます。このパターンに従うことでコードのメンテナンスやテストやデバッグがやりやすくなります。関数は小さく分離されているので再利用しやすくなり、必要に応じてどこにでもコピペできます。

これは私の意見ですが、関数型プログラミングでは必要なコード量が少なくて済みます。関数型プログラミングを使うときは、先に触れた純粋関数、無名関数、クロージャ、高階関数のコンセプトを理解しておくことが重要です。

まとめ

Reduxはアプリのステート管理のための素晴らしいライブラリであり、Reduxが多くの開発者を魅了していることは確かです。それ以外に何を知る必要があるでしょうか?

ReduxはUberやTwitterなどの企業で大規模に用いられているだけではなく、WordPressなどのプロジェクトでも実装に成功しています。Reduxはどんな既存アプリにも使える万能ライブラリではないという論争が起きていることも確かですし、そのとおりです。

シンプルなアクションだけを用いるアプリやサーバーサイドレンダリングの不要なアプリでは、おそらくReduxは不要でしょう。Reduxはアクションをコンポーネントレベルで扱いますそうしたアプリのアクションはコンポーネントレベルで扱えます。

いずれにしろReduxの素晴らしさに変わりはありません。特にReactをお使いの方ならぜひReactReduxをチェックすべきでしょう。


お知らせ: Webアプリの「監視カメラ」とも言うべきLogRocketについて

LogRocketは、ブラウザで起きた問題を再生できるフロントエンドログ出力ツールです。エラーの原因をあれこれ推測したり、ユーザーからスクショやログダンプを送ってもらったりしなくても、LogRocketでセッションを再生して素早く原因を理解できるようになります。LogRocketはフレームワークの種類を問わずあらゆるアプリで利用可能であり、プラグインを使えばReduxやVueや@ngrx/storeの追加コンテキストをログ出力することもできます。

LogRocketは、Reduxのアクションやステートの他にも、コンソールログやJavaScriptエラー、スタックトレース、ネットワークリクエスト/レスポンス(bodyとブラウザメタデータを含む)、カスタムログも記録できます。DOMのインスツルメンテーション機能でページのHTMLやCSSを記録することも、どんなに複雑なSPA(シングルページアプリ)でもピクセル単位まで完璧な動画に再作成することもできます。

関連記事

Rails: JSON Patchでパフォーマンスを向上(翻訳)

WebサイトをPWA(Progressive Web App)に変える簡単な手順(翻訳)

JavaScript: Parcel.jsでReact.jsプロジェクトを作成する(翻訳)

Vue.jsサンプルコード(34)逆ポーランド記法の電卓アプリを作る

$
0
0

34. 逆ポーランド記法の電卓アプリを作る

  • Vue.jsバージョン: 2.5.2
  • いわゆる逆ポーランド記法(RPN)方式のお遊び電卓です。
  • [入力] フィールドに数値か演算記号を入力できます。
    • 数値はスタックにプッシュされて下に表示されます。
    • 演算記号は直近の2つのスタックを演算し、2つのスタックをクリアしてから結果をスタックにプッシュします。
  • 入力した瞬間に処理されるので、事実上1桁ずつしか入力できません。
  • 計算できないときはNaNがスタックに置かれます。
  • 画面をリロードすると最初に戻ります。

サンプルコード

ポイント

HTML側でv-forを用いて繰り返しを実現しています。

          <template v-for="e in s.slice().reverse()">
            <li class="list-group-item">
              {{e}}
            </li>
          </template>
        </ul>

バックナンバー(Vue.jsサンプルコード)

Vue.jsサンプルコード(01〜03)Hello World・簡単な導入方法・デバッグ・結果の表示とメモ化

Reduxストアの概念をRubyで再実装して理解する(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

なお、reducerは「レデューサー」「リデューサー」の2とおりのカタカナがあるようですが、ネットで多数派の「レデューサー」にしました。

Reduxストアの概念をRubyで再実装して理解する(翻訳)

Reduxは最近のJavaScriptアプリのステート管理ライブラリとして人気が高まっており、実際にはその中心にシンプルなデータストアがあるだけです。Reduxのストアは他と何が違うのでしょうか?データを保存する点では他のストアと同様ですが、Reduxのデータ変更は常に「アクション」を「レデューサー」と呼ばれるものにディスパッチすることで行い、サブスクライブしているリスナーに通知します。Reduxストアの話をするときには、「レデューサー」「アクション」「リスナー」の概念を理解しておく必要があります。

レデューサー(reducer)はRedux世界でよく知られた名前であり、ステートとアクションを引数として受け取り、新しいステートを返す純粋関数を指します。ある関数が「純粋」である場合、関数に副作用がないことを意味します。純粋関数は常に新しい値を返し、受け取った値(関数に渡した元のステート)を変更することは決してありません。よく例として使われるカウンタレデューサーを見てみることにしましょう。カウンタレデューサーは、カウントアップ(increment)やカウントダウン(decrement)に応答する方法を知っており、それ以外のデータを追加しません(Gist)。

counter_reducer = -> (state, action) {
  state ||= 0

  case action[:type]
  when 'increment'
    state += 1
  when 'decrement'
    state -= 1
  else
    state
  end
}

上のRubyコードがJavaScript版と唯一異なっている点は、関数定義でlambda(無名関数)を使っていることと、アクションの定義にRubyのハッシュ(JavaScriptのオブジェクトと対になります)を用いている点です。ステートは常に引数として渡されますが、デフォルト値(ゼロからカウントアップする)も与えています。これで新しいカウンタレデューサは、カウントアップとカウントダウンのアクションに応答し、カウンタの適切な新しい値を返すようになります。ここでもうひとつ注目したい重要な点は、カウンタレデューサーは指定のアクションへの応答方法については何の知識も持たず、元のステートを無変更のまま返していることです。ここが後で重要になってきます。

新しいカウンタは次のように使います。

counter.call(2, { type: 'increment' })
#=> 3
counter.call(nil, { type: 'decrement' })
#=> -1

レデューサーを見たことのない人には上のカウンタの例が冗長に見え、アクションがハッシュで定義されている理由が謎に思えるかもしれません。しかしアクションはこれよりもっと複雑になることもあり、他にも情報を伝える必要が生じる可能性もあります。アクションを定義する追加情報はハッシュ内で他のキーの下に保存されます。つまりReduxストアのアクションは実際には1個のオブジェクトなのです(技術的に言うと上のRubyコードではハッシュになっています)。

レデューサーとアクションを理解できたので、今度はリスナーを定義してみましょう。リスナーは、内部に保持しているのが単なる関数である点を除けばまさしく想像どおりです。リスナー関数は「純粋」である必要はなく、単にストア内でステートが変更されたときの挙動を定義します。

以下の典型的なリスナーもlambdaで定義してみました。

counter_listener = -> () { puts "I am counting numbers" }

ここでは文字列を標準出力に出力しているだけですが、元々のReduxのユースケースではおそらくDOMを更新することになるでしょう。

基本的な知識を一通り押さえることができ、カウンタレデューサーとリスナーもできあがったので、基本的なReduxStoreをRubyベースで実装する準備が整いました(Gist)。

class ReduxStore
  attr_reader :current_state

  def initialize(reducer)
    @reducer = reducer
    @listeners = []
    @current_state = nil
    dispatch({})
  end

  def dispatch(action)
    @current_state = @reducer.call(@current_state, action)
    @listeners.each { |l| l.call }
  end

  def subscribe(listener)
    @listeners.push(listener)
    ->{ @listeners.delete(listener) }
  end
end

新しいReduxStoreは、作成時に渡される任意のレデューサーを使って動作する、一般的なストアです。初期化プロセスの部分は、基本的にはJavaScriptのcreateStore()に相当します。

let createStore(reducer)

createStore()は、渡されたレデューサーに応じたストアを作成して返す関数です。1つのストアで使えるレデューサーは常に1つだけなので、カウンタレデューサーは自分のステートをカウンターのストアに保存します。アクションがディスパッチされると、サブスクライブしているすべてのリスナーに通知(呼び出し)されます。以下は新しいストアの利用例です。

my_counter_store = ReduxStore.new(counter_reducer)
my_counter_store.dispatch({type: 'increment'})

最初にカウンタストアを作成し、次に、ストアのcurrent_stateを変更するincrementアクションをディスパッチします。ステートが変更されたときに何らかの操作を実行するリスナーの使い方を見てみましょう。

my_counter_store.subscribe(counter_listener)
my_counter_store.dispatch({type: 'increment'})
#=> I am counting numbers
my_counter_store.dispatch({type: 'decrement'})
#=>I am counting numbers
puts "Counter is #{my_counter_store.current_state}"
#=>Counter is 3

わずか数行のコードでRedux的なストアが準備できました。しかしReduxストアについてまだ説明していなかったことがひとつあります。Reduxストアは本質的に「アプリの全ステートの保存に用いる」ものであり、1個のレデューサーから返される1個の値のためだけのものではありません。

「ちょっと待った!1つのストアは常に1つのレデューサーの上で動作するって最初に言ってたじゃないの: 今度は複数の値を保存するってどういうこと?」

ストアの挙動をまったく変更せずに、複数のレデューサーが渡すステートを1つのストアに同時に保存できます。そのためには、複数のレデューサーを1つのルート(app)レデューサーにまとめなければなりません。こうすることで複数のストアを保存し、すべてのレデューサーが渡す値を表すツリー状の構造を更新します。先ほど、レデューサーは自分が扱えないアクションを受け取ったときには渡された値を無変更のまま返すと申し上げたのを覚えていますでしょうか?これがまさしく、1つのアクションをルートストアにディスパッチすることで、ツリーのアップデートすべき値だけをアップデートし、それ以外のすべての値を安全に保つ仕組みです。

複数のレデューサーをまとめるクラスメソッドでReduxStoreを拡張してみましょう(Gist)。

class ReduxStore
  def self.combine_reducers(reducers)
    -> (state, action) {
      state ||= {}

      reducers.reduce({}) { |next_state, (key, reducer)|
        next_state[key] = reducer.call(state[key], action)
        next_state
      }
    }
  end
end

これはReduxのcombineReducers()関数に対応するもので、別の関数を返します。返される関数は、実際にステートとアクションをパラメータとして受け取って新しいステートを再度返すレデューサーです。このときだけ、ツリー全体に渡って動作します(Rubyではネストしたハッシュ、JavaScriptではオブジェクトになります)。

実際の動作を見るために、カウンタのレデューサーと組み合わせる別のレデューサーを定義してみましょう(Gist)。

todos_reducer = -> (state, action) {
  state ||= []

  case action[:type]
  when 'add'
    state.push(action[:todo])
  when 'remove'
    state.remove(action[:todo])
  else
    state
  end
}

新しいtodos_reducerにはTODOの項目リストが保存されます。todoという追加パラメータを受け取ることで、アクションをオブジェクトの概念として利用していることにご注目ください。それでは2つのレデューサーを1つにまとめるroot_reducerを作成してみましょう。

root_reducer = ReduxStore.combine_reducers({ counter: counter_reducer, todos: todos_reducer })

レデューサーを再び1つにできたので、アプリのストアを作成できるようになりました。

app_store = ReduxStore.new(root_reducer)
app_store.dispatch({type: 'increment'})
app_store.dispatch({type: 'add', todo: 'Buy milk'})
app_store.dispatch({type: 'increment'})
app_store.current_state
# => {:counter=>2, :todos=>["Buy milk"]}

ルートのレデューサーを作成したことで、1つのルートハッシュに2つのレデューサーのステートを両方とも保存できました。そしてこれが、複数のレデューサーを実装してアクションやリスナーとまとめることで、ツリー状の構造を持つ唯一のストアをビルドする方法です。

私の記事が、Reduxストアとは一体何かについて関心のお持ちの方がReduxの背後のコンセプトを理解する助けになることを願っています。

関連記事

JavaScript: Reduxが必要なとき/不要なとき(翻訳)

[Ruby]クロージャーを使ってブロックを1回だけ実行する


週刊Railsウォッチ(20180316)Rails 5.2のドキュメント更新中、Value Objectの使い方、RubyがTIOBEトップテン復活、Rails「雪だるま」エンコーディングほか

$
0
0

こんにちは、hachi8833です。先週終点で車両の座席に置き忘れたiPhone 7が粉々になって戻ってきて風景がぐらりとかしいだ気がしましたが、補償が効いて本体交換できてケロッと立ち直りました。春ですねぇ。

春たけなわのウォッチ、いってみましょう。

Rails: 今週の改修

5.2はまだ出ていませんが、ドキュメント更新が増えていて、収束に近づいていることを感じさせます。今週も5.2-stableと6.0向けmasterの両方から見繕いました。いずれも変更の可能性がありますので。

Rails 5.1->5.2アップグレードドキュメント

まずは5.2-stableから。CSPとドキュメント周りの改修が目立ちます。

一応現時点のアップグレードドキュメントです↓。今のところ作業量は少なくて済みそう。

Rails 5.1からRails 5.2へのアップグレード
* Bootsnap

#29313でRails 5.2からBootsnap gemが含まれるようになりました。app:updateタスクはboot.rbで設定されます。使いたい場合はGemfileにこのgemを追加し、使わない場合はBootsnapを使わないようにboot.rbを変更してください。

  • cookie値に署名済みまたは暗号化cookieの有効期限が設定されるようになった

セキュリティ向上のため、署名済みまたは暗号化cookieの値に有効期限の情報が埋め込まれるようになりました。これによって、5.2より前のRailsとcookieバージョンの互換性が失われます。5.1以前のcookieが必要な場合や、5.2デプロイを検証中でロールバックの道を残しておきたい場合は、Rails.application.config.action_dispatch.use_authenticated_cookie_encryptionfalseに設定してください。

同コミットより大意


つっつきボイス:bootsnapはShopifyのgemがRailsで標準採用になったやつですね」「Shopfyはカナダのオタワですって」「運用中のサーバーでcookieの互換性が失われると、挙動としてはたとえば強制ログアウトが発生したりとか」「ソシャゲみたいにユーザーがめちゃ多いサービスでcookieが一斉に切れるとヤバイ: ユーザーが再ログインしようとして一気に押しかけて、ログインサーバーに負荷が集中してお亡くなりになったりとか」

その後ロードバランサーなどの話題になりました。

Railsエンジンを場所を変えてマウントできるようになった

# actionpack/lib/action_dispatch/routing/mapper.rb#L652
           def define_generate_prefix(app, name)
             _route = @set.named_routes.get name
             _routes = @set
-            app.routes.define_mounted_helper(name)
+
+            script_namer = ->(options) do
+              prefix_options = options.slice(*_route.segment_keys)
+              prefix_options[:relative_url_root] = "".freeze
+              # We must actually delete prefix segment keys to avoid passing them to next url_for.
+              _route.segment_keys.each { |k| options.delete(k) }
+              _routes.url_helpers.send("#{name}_path", prefix_options)
+            end
+
+            app.routes.define_mounted_helper(name, script_namer)
+
             app.routes.extend Module.new {
               def optimize_routes_generation?; false; end
+
               define_method :find_script_name do |options|
                 if options.key? :script_name
                   super(options)
                 else
-                  prefix_options = options.slice(*_route.segment_keys)
-                  prefix_options[:relative_url_root] = "".freeze
-                  # We must actually delete prefix segment keys to avoid passing them to next url_for.
-                  _route.segment_keys.each { |k| options.delete(k) }
-                  _routes.url_helpers.send("#{name}_path", prefix_options)
+                  script_namer.call(options)
                 end
               end
             }

これは実は昨年のコミットですが、#793c11dのコミットメッセージで目に止まったので。


つっつきボイス: 「同じマウンタブルエンジンを別名でマウントできるようになったと」「マウンタブルエンジン使うのって、Sidekiqの管理画面をマウントするときぐらいだけどなっ」「あとletter_opener導入するとエンジン入ってブラウザで見られますね」

CSPがWelcomeページやmailerプレビュー表示を邪魔しないよう修正

# railties/lib/rails/application_controller.rb#L7
+  before_action :disable_content_security_policy_nonce!
+
+  content_security_policy do |policy|
+    if policy
+      policy.script_src :unsafe_inline
+      policy.style_src :unsafe_inline
+    end
+  end
...
+    def disable_content_security_policy_nonce!
+      request.content_security_policy_nonce_generator = nil
+    end

つっつきボイス: 「こうやって追いかけているとわかりますが、最近のRailsではこういうCSP周りがちょくちょくアップデートされてますね」

CSPをコントローラからオフにできる機能を追加

# actionpack/lib/action_controller/metal/content_security_policy.rb#L16
     module ClassMethods
-      def content_security_policy(**options, &block)
+      def content_security_policy(enabled = true, **options, &block)
         before_action(options) do
           if block_given?
             policy = request.content_security_policy.clone
             yield policy
             request.content_security_policy = policy
           end
+
+          unless enabled
+            request.content_security_policy = nil
+          end
         end
       end

つっつきボイス: 「オンにできるならオフにできないとね」「たしかに」

CSPポリシーインスタンスを常にyieldするように変更

# actionpack/lib/action_controller/metal/content_security_policy.rb#L17
       def content_security_policy(enabled = true, **options, &block)
         before_action(options) do
           if block_given?
-            policy = request.content_security_policy.clone
+            policy = current_content_security_policy
             yield policy
             request.content_security_policy = policy
           end
...
+
+      def current_content_security_policy
+        request.content_security_policy.try(:clone) || ActionDispatch::ContentSecurityPolicy.new
+      end

つっつきボイス:cloneやめて常に同一のCSP設定を参照できるようにしたと: でないと挙動を追ったり変更したりできないですからね」

i18nドキュメント更新


  • 存在しなくなったGlobalize::Backend::Staticへの参照を削除
  • Google Groupsへの参照を削除
  • Globalize3への参照を削除(紛らわしいので)
  • 保存したコンテンツの翻訳方法についてのセクションを追加

本ガイドに記述されているI18n APIは、主にUI文字列の翻訳への利用を意図しています。モデルのコンテンツの翻訳手法をお探しの場合は、UI文字列とは別のソリューションが必要です。
モデルコンテンツの翻訳で役立ついろいろなgemがあります。

  • Globalize: 翻訳用の別テーブルに訳文を保存できます。1つのテーブルが1つの翻訳済みモデルになります。
  • Mobility: 訳文用テーブルやJSON columns(PostgreSQL)などさまざまな形式で訳文を保存できます。
  • Traco: Rails 3や4向けの翻訳可能なカラムを使えるようにします。カラムは元のテーブル自身に保存します。
    ガイド更新箇所より大意(強調はTechRacho編集部)

i18nのサードパーティgemがいくつか公式ガイドに載ったのが目に止まりました。


つっつきボイス: 「お、i18n gemを紹介してくれるようになるのか!」「公式が推してくれるのはうれしいっすね」「今のところGlobalizeがメジャーらしいです」「↑上にも書いてますがUI翻訳のyamlはRailsでサポートするけどコンテンツの方はRailsがサポートすることは今後もないだろうから、こういう形にしたのかも」
「モデルのi18nは自力で実装するとつらいよw: 昔やったけど当時はこういうgemなかったんで」「あ、例のMangaRebornですね」「たとえばサイトにデフォルト言語を設定したりとか、フォールバックする言語を指定したりとか必要になってくるので」「台湾語がなければ中国語、みたいな」
「Mobilityは例のshioyamaさんです↓: 名字のSalzbergをそのままもじってますね」

RubyのModule Builderパターン #1 モジュールはどのように使われてきたか(翻訳)

「こういうi18n gemは、コードを追うまではしないとしても、どういうインターフェイスを用意しているかという部分に注目して比較してみると結構勉強になりますよ: みんなそれぞれ個性があって」

ルーティングガイド更新

Railsのルーティング設定

アプリやエンジンのルーティングはconfig/routes.rbに保存されます。以下は典型的な外観です。

Rails.application.routes.draw do
  resources :brands, only: [:index, :show]
    resources :products, only: [:index, :show]
  end

  resource :basket, only: [:show, :update, :destroy]

  resolve("Basket") { route_for(:basket) }
end

これは普通のRubyソースファイルなので、Rubyのあらゆる機能を用いてルーティングを定義できますが、変数名がルーターのDSLと衝突しないようにご注意ください。
メモ: ルーティング定義を囲むRails.application.routes.draw do ... endブロックは、ルーターのDSLがスコープを確立するために必要なので絶対に削除しないでください。
ガイド更新箇所より大意(強調はTechRacho編集部)


つっつきボイス: 「Railsのルーティングの包括的というか完全なドキュメントが欲しいっすねマジで: 機能はやたらめったらあるけど、知らないと使いようのない機能の多さではRails内ではトップかも」
「今頃変数名にはご注意…だと?」「asとか使うとヘルパーが自動生成されたりとかゴロゴロありますからねー」「まRailsに慣れてくると『この語はキケン』みたいなのをだんだん身体で思い知るけど」「(´・ω・`)」

Railsのルーティングを極める(前編)

「そうそう、sheepみたいに単数形複数形が同じ語を使うと、生成されるヘルパー名が通常と違ってくることあります」「え~~!」

resources :penguins

# 通常は以下が生成される
penguin_path(@penguin)
penguins_path
resources :sheep

# 
sheep_path(@sheep)
sheep_index_path   # 区別のため「_index」が付く

「ActiveSupportにそういう活用形をチェックするメソッドがある↓」「それは知ってたけど…くぅ」「活用形といえばdataは複数形で、単数形はdatum: みんなもう知ってるよね!」

[Rails5] Active Support::Inflectorの便利な活用形メソッド群

ラテン語由来の英単語はたいてい不規則活用になりますね。symposionとsymposiumとか。ちょっと話はそれますが、indexの複数形はindicesが正式とされていますが、近年急速にすたれつつある印象です。

ActiveSupport::Cache::Entryをメモ化してマーシャリングの負荷を軽減

これは5.2-stableとmasterの両方に入っていました。ここからはmasterです。

# activesupport/lib/active_support/cache.rb#L806
+        def marshaled_value
+          @marshaled_value ||= Marshal.dump(@value)
+        end

メモ化といえば、おなじみ「縦縦イコール」ですね。


つっつきボイス: 「kazzさんが以前『たてたてイコール』って呼んでたのが可愛かったのでw」「本当は何て言うんだっけ?」「『オアイコール』?」
「ちなみに以前も話したことあるけど、Marshal.dumpはRubyのバージョンが変わると互換性が失われることがあるので、データベースにそのまま保存すると後で痛い目に遭うかもよ」「怖!」

起動メッセージの無意味な「Exiting」を除去

# railties/lib/rails/commands/server/server_command.rb#L158
           if server.serveable?
             print_boot_information(server.server, server.served_url)
-            server.start do
-              say "Exiting" unless options[:daemon]
-            end
+            after_stop_callback = -> { say "Exiting" unless options[:daemon] }
+            server.start(after_stop_callback)
           else
             say rack_server_suggestion(using)
           end

rails routes --expandedの横線をきれいにした

$ rails routes --expanded
--[ Route 1 ]------------------------------------------------------------
-------
(snip)
--[ Route 42 ]-----------------------------------------------------------
--------
(snip)
--[ Route 333 ]----------------------------------------------------------
---------
(snip)
$ rails routes --expanded
--[ Route 1 ]------------------------------------------------------------
(snip)
--[ Route 42 ]-----------------------------------------------------------
(snip)
--[ Route 333 ]----------------------------------------------------------
(snip)

つっつきボイス: 「前は横棒固定か」「IO.console.winsizeって初めて知った: これならターミナルに合わせて調整できるし」「地味だけどありがたい修正!」

+        previous_console_winsize = IO.console.winsize
+        IO.console.winsize = [0, 23]

参考: Rubyリファレンスマニュアル IO.console

rails routes -gで結果が空の場合のメッセージを修正

  • ActionDispatch::Routingのドキュメント更新
    • -gの記述を追加
    • rails routes--expandedオプションの説明を追加
  • ActionDispatch::Routing::ConsoleFormatter::Baseの導入
    • Baseを作ってSheetExpandedで継承し、コード重複を防止
      • Expandedのコンポーネントで末尾の”\n”を削除
      • Expanded#headerの戻り値を@bufferからnilに変更
    • -gのときのno_routesメッセージがよくなかったので修正
      • -cの場合のメッセージは「Display No routes were found for this controller」
      • -gの場合のメッセージは「No routes were found for this grep pattern」

PRメッセージより大意

# actionpack/lib/action_dispatch/routing.rb#L85
         def normalize_filter(filter)
-          if filter.is_a?(Hash) && filter[:controller]
+          if filter[:controller]
             { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ }
-          elsif filter
-            { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ }
+          elsif filter[:grep_pattern]
+            {
+              controller: /#{filter[:grep_pattern]}/,
+              action: /#{filter[:grep_pattern]}/,
+              verb: /#{filter[:grep_pattern]}/,
+              name: /#{filter[:grep_pattern]}/,
+              path: /#{filter[:grep_pattern]}/
+            }
           end
         end

つっつきボイス: 「最近自分はブラウザで/rails/info/routesで見ちゃうこと多いかなー」「このパスが割りと覚えにくいという」「/aとか打ってルーティングエラー出す方が早いっすね」「たしかに」

Rails

Railsビューをin_groups_ofでリファクタリング(RubyFlowより)

// 同記事より
%table.sponsors{width: "100%;"}
  - sponsors_by_level.levels.each do |level|
    - level.sponsors.in_groups_of(level.sponsors_per_line, false) do |group|
      %tr
        - group.each do |sponsor|
          %td{colspan: 12 / group.size, style: "text-align: center !important;"}
            = link_to sponsor.path do
              = image_tag(sponsor.logo_url, alt: sponsor.name, title: sponsor.name, style: "display: inline; float: none;")
    %tr
      %td{colspan: 12}
        %hr

つっつきボイス: 「ほっほー、in_groups_ofとな」「内部でeach_slice使ってるからこれを直接使う方が早かったかも、だそうです」「改修前のhaml、見たくないやつ…」

参考: in_groups_of
参考: Rubyリファレンス・マニュアル each_slice

RailsのシステムテストでJSエラーをキャッチする方法(RubyFlowより)

WARN: javascript warning
http://127.0.0.1:60979/assets/application.js 9457 Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience.
Got 3 failures and 1 other error from failure aggregation block “javascript errrors”:
1) http://127.0.0.1:60481/sso 10:18 Uncaught SyntaxError: Unexpected token ;
同記事より


つっつきボイス: 「テーブル使うのはどうかと思うけどまあそれはおいといて」「どうやらきれいにキャッチする方法がないからコンソールに出力してそっちで見れ、ってことみたい」「たしかに原理的に難しそう」

Railsのメール設定でdeliverdeliver_nowは使うな(Hacklinesより)

deliver_laterにしとけ、だそうです。

# 同記事より
class User
  after_update :send_email
  def send_email
    ReportMailer.update_mail(id).deliver_later
  end
end

つっつきボイス:deliver_nowは同期的なのか: じゃあ使いたくないやつですね」「deliver_laterで非同期になると、それはそれでテストで考慮しないといけない点が増えて大変になるけど: キューに入ったりメールサーバーが応答したりしてもそれだけでよしとできないとか」「キューに入ってコケたかどうか、とか」

参考: Rails API deliver_later

RailsでReduxのフォームを使うには(Awesome Rubyより)


redux-form.comより

# 同記事より
    def create
        authorize resource_plan, :create?
        command = GlobalContainer['plan.services.create_plan_command'] #Plan::CreatePlan.new
        respond_to do |format|
          format.json {
            command.call(resource_plan, params[:plan]) do |m|
              m.success do |plan|
                flash[:notice] = t('messages.created', resource_name: Plan.model_name.human)
                render json: { id: plan.id}, status: :ok, location: settings_plan_path(plan)
              end
              m.failure do |form|
                render json: {
                  status: :failure,
                  payload: { errors: form.react_errors_hash }
                }, status: 422
              end
            end
          }
        end
      end

Redux-Formでうまく書けたそうです。Redux-Formは完成途上らしいので、末尾でFinal Formというフレームワーク非依存JSフォームライブラリも紹介しています。


github.com/final-form/final-formより


つっつきボイス: 「コードの中でRepresenterというのを置いてますね」

HABTMをhas_many throughに置き換える(RubyFlowより)

# 同記事より
class PostTag < ApplicationRecord
  belongs_to :post
  belongs_to :tag
end
class Post < ApplicationRecord
  has_many :post_tags, -> { order(rank: :asc) }
  has_many :tags, through: :post_tags
end
class Tag < ApplicationRecord
  has_many :post_tags
  has_many :posts, through: :post_tags
end

つっつきボイス: 「絵に描いたようなHABTMリファクタリングですが、基本ということで」

参考: 仕事のねた: rails3でHABTMが非推奨になってる

Railsで巨大データをdedupする(Hacklinesより)

# 同記事より
# log.rb
class Log < ActiveRecord::Base
  has_many :user_logs

  def store(data)
    key = Digest::MD5.hexdigest(data)
    log = Log.find_by_checksum(key)
    if log.nil?
      log = Log.new(data: data, checksum: key)
      Log.transaction(requires_new: true) do
        begin
          log.save!
        rescue ActiveRecord::RecordNotUnique => e
          raise ActiveRecord::Rollback
        end
      end
    end
    log
  end
end

ActiveRecordリレーションをyield_selfでコンポジション可能にする(Hacklinesより)

# 同記事より
def call
  base_relation.
    joins(:care_periods).
    yield_self(&method(:care_provider_clause)).
    yield_self(&method(:hospital_clause)).
    yield_self(&method(:discharge_period_clause))
end

private

def care_provider_clause(relation)
  if params.care_provider_id.present?
    relation.where(care_periods: { care_provider_id: params.care_provider_id })
  else
    relation
  end
end
...

つっつきボイス:yield_selfってどっかで見たゾ」「あーこれだ↓」「そうそう、tapしないで書けるというのはちょっといいかも」「tapだとビックリマーク付きのwhere!になりますね: 破壊的にならないからいいだろ?っていう趣旨なのかな」「別に破壊的でもいい気はするけど」「メモリ効率とかの話を別にすれば、イミュータブルな方が望ましくはあるし、where!は基本使いたくはないので気持ちはわかる」

Ruby 2.5の`yield_self`が想像以上に何だかスゴい件について(翻訳)

RailsアプリをHerokuからAWSに移して年8万ドル以上節約した件について(Awesome Rubyより)


つっつきボイス: 「1000万近くって以前どれだけザルだったのかとw」「ちゃんと読んでないけど、HerokuとAWSの違いというより設定が大きかったんじゃ?」

search_flip: ElasticSearchクエリをチェインするクライアント(RubyFlowより)

★はまだ少ないです。


つっつきボイス: 「類似のgemがあるんではないかと思って」「ははあ、searchkickと違ってハッシュ使わずに書けるぞ↓ドヤアってことかな」

# 同リポジトリより
# elasticsearch-ruby
Comment.search(
  query: {
    query_string: {
      query: "hello world",
      default_operator: "AND"
    }
  }
)

# searchkick
Comment.search("hello world",
               where: { available: true },
               order: { id: "desc" },
               aggs: [:username])

# search_flip
CommentIndex.where(available: true)
            .search("hello world")
            .sort(id: "desc")
            .aggregate(:username)

RabbitMQはSidekiqの単なる置き換え以上のものだ(RubyFlowより)


つっつきボイス: 「熱烈にRabbitMQ推してますね: 永続性の保証とかで違ってくるみたい」「Sidekiqだって用途に合ってればとってもいいヨって最後に書いてますね」


rabbitmq.comより


sidekiq.orgより

Railsデプロイ前にこれだけはチェックしたい5項目(RubyFlowより)


  • public/404.htmlとか設定したか
  • HTTPSにしたか
  • URLでデータベース内容がお漏らししないようにしたか
  • 監視設定やったか
  • デプロイを自動化したか
# 同記事より
class User < ApplicationRecord
  has_secure_token :uuid # DBでUNIQUE indexにしておけばベスト

  def to_param
    self.uuid
  end
end

つっつきボイス: 「年バレネタですが『ウルトラ5つの誓い』を思い出しちゃって」

参考: ウルトラ5つの誓いとは (ウルトライツツノチカイとは) [単語記事] - ニコニコ大百科

RubyのValue Objectはこう使おう(RubyFlowより)

# 同リポジトリより
# Good
BigDecimal('100').to_i     # => 数値を変えずに精度だけ下げる
# Bad
Quantity.new(10, 'm').to_i # => コンテキストが失われる: Quantity#amountとする方がずっといい

# Acceptable
Dates::Period.to_activercord # => コンテキストによってはあり
# Questionable
Dates::Period.to_regexp      # => #regexpでいいんじゃね?

つっつきボイス: 「けっこうがっつり書いてあってよさそうです」「なぜGitHubリポジトリなのかはおいといて」「あのzverokさんだ↓」

Ruby: 「マジック」と呼ぶのをやめよう(翻訳)

QuickType.io: JSONを貼るとRubyやSwiftやJSのコードに変換するサイト(RubyFlowより)


同サイトより

Rubyの場合、例のdry-rbを使ってくれます。

  • 変換元


同サイトより

  • 変換先


同サイトより


つっつきボイス: 「お、これ便利かも」「Pythonが入ってないのが何となく男らしい」「逆変換もできたらいいな♡」

悪いのはRailsじゃない、Active Recordだっ(Hacklinesより)

# 同記事より
data = [
  { name: "Owner", email: "owner@example.com" },
  { name: "Employee", email: "employee@example.com" },
  ...
]

# Raw SQL
INSERT INTO users (name, email) 
VALUES ("Owner", "owner@example.com"), ("Employee", "employee@example.com")

# Sequel
  db[:users].multi_insert(data)

# ActiveRecord by #import
  User.import(data.first.keys, data.map(&:values))

# Arel
  table = Table.new(:users)
  manager = Arel::InsertManager.new
  manger.into(table)

  manager.columns = [table[:name], table[:email]]
  manager.values = manager.create_values_list(data.map(&values))
  • 結局SQL構文知らないと使えない
  • RubyによるSQL構文チェックがない
  • オブジェクト指向じゃない
  • メンテがつらい
  • モデルにビジネスロジックだのエンティティ構造だの追加アクションだのしょぼいロジック定義が山盛りになる

つっつきボイス: 「PV狙いのタイトルっぽい」「悪いとしたらビュー周りかなと思った」「この人『Arelは悪くない、Sequelいいヤツ』って言ってますけど、Arelについてはちょっとどうかなー」「うーむ」「私は最終的には生SQLが最強だとこっそり信じてますけど」「生SQLは覇者」「ORMでやってても結局SQLチェックしますしね」「文中のRecursive Common Table Expressionって何だったかな(↓)」

参考: COMMON_TABLE_EXPRESSION (TRANSACT-SQL) | Microsoft Docs

私がRailsよりHanamiが好きな理由(Ruby Weeklyより)


hanamirb.orgより

  • Repositoryパターンなところ
  • アクションがクラスであるところ
  • ビュークラスがあるところ

著者はあのRyan Biggさんです↓。

Railsの`CurrentAttributes`は有害である(翻訳)

SOLIDの原則その2: オープン/クローズの原則

# 同記事より
class UserCreateService
  def initialize(params, validator: UserValidator)
    @params = params
    @validator = validator
  end

  def call
    return false unless validator.new(params).validate
    process_user_data
  end

  attr_reader :params, :validator

  def process_user_data
    ...
  end
end

RubyMineで速攻殺している自動チェック項目3つ(Hacklinesより)

とても短い記事です。


つっつきボイス: 「Cucumberとかスペルチェックはともかく、”Double quoted string”って式展開がない場合はシングルクォートにしろっていうアレですかね?」「RuboCopちゃんに怒られるから基本シングルクォートにする癖がついてる」「実はオフにする方法を知らないけどなっ: 玉突きで変更しないといけないし」

Rubyスタイルガイドを読む: 数値、文字列、日時(日付・時刻・時間)

海外のRuby/Railsカンファレンス

Ruby/Rails関連おすすめ情報源(RubyFlowより)

書籍やサイトやチュートリアルがずらっと並んでいます。

あるRails開発会社の会社概要(RubyFlowより)

開発ツールや進め方が割りと事細かに書かれています。全部字ばっかりなのが逆に珍しいかも。

ビューやヘルパーは別世界か


つっつきボイス: 「ちょうど最近この辺の話をよくしてたので」「そうそう、#lとか#tみたいにビュー全体で使うようなのをヘルパーに置くのはまだわかるんだけど、『これ汚いからビューから逃したい』みたいなのをグローバルなヘルパーに置くのはどうかな~っていつも思ってる」「さすがヘルパー嫌いマン」「Railsのヘルパーは、WordPressで言うfunctions.phpみたいなものって説明してもらって腑に落ちたことあります」
「a_matsudaさんといえばactive_decoratorの作者ですよね: モデル名と紐付いているデコレータのモジュールをビューに行くまでにこっそりインクルードするみたいな: まさにそういう話ですよね↑」「俺それ正解だと思うよマジで」

「ヘルパーに置いてグローバルになるくらいなら、いっそコントローラに書いちゃいますね: helper_methodっていうメソッド↓があって、これを使うとコントローラにヘルパーを書けちゃうんですよ」「へー!」「知らなかった」「しょっちゅうは使わないけど、ここぞというときに控えめに使う感じで: 内部の挙動はまだよく知らないし本当にいいものかどうかはちょっと微妙なんですが」

参考: Rails API helper_method

class ApplicationController < ActionController::Base
  helper_method :current_user, :logged_in?

  def current_user
    @current_user ||= User.find_by(id: session[:user])
  end

  def logged_in?
    current_user != nil
  end
end

Railsの「雪だるまエンコーディング」問題が修正☃️

先ほど流れてきたので。

その他小粒記事

Ruby trunkより

特殊変数を排除してPathnameを高速化(継続)

Regexp#=~だと$&などの特殊変数を更新する分オーバーヘッドが生じるので、更新しないRegexp#match?に置き換えたとのことです。ベンチマークの書式がびしっと整ってます。

[Ruby] Kernelの特殊変数をできるだけ$記号なしで書いてみる


つっつきボイス: 「ちょっと話それるけど、名前がFileなのにパスっぽいものも渡されたりするとどうかと思うことがある」「それは確かによくないかも」

提案: キーがない場合にraiseする#dig!(継続)

hash = {
    :name => {
        :first => "Ariel",
        :last => "Caplan"
    }
}

hash.dig!(:name, :first) # => Ariel
hash.dig!(:name, :middle) # => nil   ●これをraiseしたい
hash.dig!(:name, :first, :foo) # raises TypeError (String does not have #dig method)

「キーワード引数でできるのでは?」「deep_fetch gemでできる」という回答です。


つっつきボイス: 「ビックリマーク付きの#dig!か」「!はこの場合いいんだろうか?」

Ruby:「プリマドンナメソッド」の臭いの警告を私が受け入れるまで(翻訳)

提案: begin(またはdo)-elseendrescueが抜けてたらsyntax errorにしたい(受理)

begin
  p :foo
else
  p :bar
end

# => :foo
# => :bar

joker10071002さんです。特にdoで始まるときにrescueを置き忘れやすいので、syntax errorにしたいとのことです。


つっつきボイス: 「自分的にはwarningのままの方がいいかなーという気がするけど」「そういえばエラー処理のelseensureってどう違うんでしたっけ?」「elseは上のどれでもなかった場合で、eusureは結果にかかわらず必ず実行するやつだったかと」

なおその後acceptされました。

参考: Rubyリファレンスマニュアル begin

Ruby

RubyにもGolangのdeferが欲しいので作ってみた話


つっつきボイス: 「ちょうど上の話にも通じてる」「Goのdeferはブロックの外にも置けてRubyのensureより強力な印象ですね: まだ使ったことないけど」「JavaScriptのPromiseもDeferredって呼ばれてた」

参考: Golang の defer 文と panic/recover 機構について - CUBE SUGAR CONTAINER
参考: 非同期処理とPromise(Deferred)を背景から理解しよう - hifive

licensed: GitHub自ら提供する依存関係のライセンス照合/キャッシュgem(Ruby Weeklyより)

# 同リポジトリより
$ bundle exec licensed status
Checking licenses for 3 dependencies

Warnings:

.licenses/rubygem/bundler.txt:
  - license needs reviewed: mit.

.licenses/rubygem/licensee.txt:
  - cached license data missing

.licenses/bower/jquery.txt:
  - license needs reviewed: mit.
  - cached license data out of date

3 dependencies checked, 3 warnings found.

まだ1か月経ってない新しいgemです。類似のgemを取り上げたことがありました。

rakeタスクをきれいに書くコツ(Hacklinesより)

DHHのYouTubeチャンネルでヒントを得たそうです。

asciidoctor: AsciiDoc形式テキストプロセッサのRuby版(Awesome Rubyより)

= Hello, AsciiDoc!
Doc Writer <doc@example.com>

An introduction to http://asciidoc.org[AsciiDoc].

== First Section

* item 1
* item 2

[source,ruby]
puts "Hello, World!"

AsciiDocはこんな感じ↑で書けるようです。単なるMarkdownの置き換えではないと言ってます。

参考: What is AsciiDoc? Why do we need it? | Asciidoctor
参考: 脱Word、脱Markdown、asciidocでドキュメント作成する際のアレコレ

ヒアドキュメントで式展開#{}を展開させない方法(Hacklinesより)

# 同記事より
venue = "world"
str = <<-'EOF'
I'm the master of the #{venue} !
No you're dead bro..\n
EOF

2018年のRuby GUI開発事情(RubyFlowより)


saveriomiroddi.github.ioより

なお著者はGobyのcontributorであることを今思い出しました。

RubyのリゾルバでSSRFフィルタをバイパスされる脆弱性(Hacklinesより)

# 同記事より
irb(main):008:0> Resolv.getaddresses("127.0.0.1")
=> ["127.0.0.1"]
irb(main):009:0> Resolv.getaddresses("localhost")
=> ["127.0.0.1"]
irb(main):010:0> Resolv.getaddresses("127.000.000.1")
=> [] # 😱

参考: Rubyリファレンスマニュアル Resolv

SCSSコンパイラを自力で書いてみたお(RubyFlowより)


同記事より

RubyがTIOBEのトップ10言語に返り咲く(Hacklinesより)


tiobe.comより


つっつきボイス: 「記事のグラフ見ると、むしろC言語がいったん下がってからガッと上がっているのが気になる」「JavaとC以外はまだ混戦かなー」

Aaron Pattersonさんから

#kind_ofが悪手になる場合

どこかで「#is_a?は今は非推奨」とmatzがツイートしていた気がしましたが、そのエイリアスである#kind_of?もどうやらあまり使って欲しくない様子です。


つっつきボイス: 「Matzが#kind_of?警察やってる」「#is_a?が非推奨になったのは、確か名前がよくなかったからだったような」「『純粋なオブジェクト指向ならメッセージベースでやろうぜ』『クラスなんてものはオブジェクト間の通信には本来不要である』という趣旨なんでしょうね」
「質問者の方もそうだけど、Javaから来ると型チェックしたくなる気持ちはわかる」「Javaにはインターフェイスがあるから」「Rubyにはrespond_to?がある」

私もつい型チェック的思考に傾きかけてたかも。反省。

参考: RubyリファレンスマニュアルObject#respond_to?


「ところでRailsには?なしのrespondo_toというのがあってですね」「紛らわし!」「Railsを先にやると、Rubyのrespond_to?の方でむしろ首を傾げたりとか」「RSpecのrespond_toマッチャーも同じ過ぎるし」「名前がこれだけ似てて意味がまるで違うという」

参考: Rails API respond_to

Ruby生誕25周年記念: コミットのビジュアル表示

SQL

データベースのモデル化アンチパターン3種(Postgres Weeklyより)


  1. Entity Attribute Values
  2. Multiple Values per Column
  3. UUID

つっつきボイス: 「EAVはSQLアンチパターンにも載っている定番中の定番っすね: 一度はやりたくなってしまうやつ」「略語になってるんですね」
「むかーしエンタープライズ系のJavaの本で『EAVはベストプラクティスのひとつである』みたいな記述があったんですが」「マジかーw」「いや、たぶんこれはメモリ構造に乗せてEAVする分にはよかったはず: RDBMSでやるもんじゃないですよもちろん」「後で検索が必要になったときに死ねるやつ」
「2.は今のRDBMSなら普通にできたりしますね: PostgreSQLのArrayとか」

PostgreSQLの全文検索でVACUUMを使うときにやるべきこと(Postgres Weeklyより)


つっつきボイス: 「出たーVACUUM ANALYZE: めちゃめちゃ重い」

PostgreSQLの新機能「シーケンス」のメリットと落とし穴(Postgres Weeklyより)

動画: データベースの隠し技紹介(Postgres Weeklyより)

オーストラリアでこの3月に行われたRubyカンファレンスであるRubyConf Auでの発表です。

JavaScript

prettier: JS界のRuboCop


同リポジトリよりより

★めちゃ多いです。

JavaScriptのエレガントな「ROROパターン」(Frontend Weeklyより)

割りと長い記事です。ROROは「Receive an object, return an object」だそうです。RubyのPOROとは違いました。

スーパー速い「Radi.js」フレームワークを作ったお(JavaScript Weeklyより)

Virtual DOMを使わないことで速くしたそうです。

Glimmer.jsとPreact.jsのパフォーマンス比較(JSer.infoより)

Linkedinの技術ブログです。

CSS/HTML/フロントエンド

Tumult Hype: フロントのアニメーション表示を徹底制御(Hacklinesより)

<a https://tumult.com/hype/”>
同記事より

HoudiniプロジェクトのCSS Paint API(Frontend Focusより)

Chrome 65以降でないと動かないので、brew cuで速攻Chromeをアップグレードしました。以下で「Fail」が出るブラウザではできないそうです。

See the Pen CSS Paint API Detection by Will Boyd (@lonekorean) on CodePen.

動画: Chromeの新機能「Local Overrides」でパフォーマンス上の仮説をテストする(Frontend Focusより)

「King’s Pawn Game」に学ぶUIデザイン(Frontend Weeklyより)


同記事より

UIデザイナー向けの記事です。King’s Pawn Gameは、チェスの序盤の定石のようです。

参考: Wikipedia-en King’s Pawn Game

CSSで四隅を切り欠くには(Frontend Focusより)

See the Pen Notched Boxes by Chris Coyier (@chriscoyier) on CodePen.

World Wide Webが29歳の誕生日(Frontend Focusより)

その他

Stackoverflowのアンケート結果

かなり長いです。

MacのiTerm2で出力を任意のエディタに送り込む(Hacklinesより)

AppleScriptの小ネタです。

若手開発者サバイバルガイド: コードが動かないときにうまく先輩に伝えるには(Hacklinesより)


つっつきボイス: 「これも翻訳打診してみますね」

HomebrewとPythonバージョンの混乱

Mathpix snipping tool: 数式を撮影するとLaTeXに変換するスマホアプリ

よく見たら昨年からiPhoneにインストールしてました。どっちかというとソルバーです。

番外

「いいこと聞いた」と思うかどうかが分かれ目?

ソイレントといえば

私の年だとソイレント・グリーンですが、ゼノギアスの方が有名っぽいですね。

たけのこ

巨星墜つ

モンティ・パイソンに出演したホーキング博士も素敵でした。


今週は以上です。

バックナンバー(2018年度)

週刊Railsウォッチ(20180309)RubyGems.orgのTLS 1.0/1.1接続非推奨、2年に1度のRailsアンケート、DockerのMoby Project、Ruby拡張をRustで書けるruruほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

Awesome Ruby

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Postgres Weekly

postgres_weekly_banner

Frontend Weekly

frontendweekly_banner_captured

Frontend Focus

frontendfocus_banner_captured

JavaScript Weekly

javascriptweekly_logo_captured

JSer.info

jser.info_logo_captured

週刊Railsウォッチ(20180323)Rails 5.2.0 RC2リリース、「サーバーレスなRubyが欲しい」、capybara風JSテストフレームワークCypressほか

$
0
0

こんにちは、hachi8833です。明日のRails Developers Meetup 2018準備で青ざめてます。

今週から記事数をできるだけ一定に保つようにいたしました。腹八分目のウォッチ、いってみましょう。

Rails: 今週の改修

Rails 5.2.0 RC2リリース

5.2.0はほぼ仕上がったそうです。

大きな変更はTechRachoでもおおよそ取り上げたので、5.2 RC2リリースノートの中からこれまで取り上げていなかったコミットを中心に見ていきます。

capify!の非推奨化

Capistrano 3でコマンドがcap installに変わったことへの対応です。!なしは元々なかったんですね。

# railties/lib/rails/generators/actions.rb#L229
      def capify!
+        ActiveSupport::Deprecation.warn("`capify!` is deprecated and will be removed in the next version of Rails.")
         log :capify, ""
         in_root { run("#{extify(:capify)} .", verbose: false) }
       end

つっつきボイス: 「そうそう、昔のCapistranoではcapファイルの作成とか設定とかデプロイに使うコマンドがcapifyでしたね」「あれ、CapistranoってRails標準だったかな?」「あ、そういえば: と思ったらGemfileではデフォルトでコメントアウトされて↓るからオプション扱いですね」

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

「ところでCapistranoって最近それほど見かけない気がしてましたが」「いやいや、もう当たり前に使われまくっててことさら話題になってないだけ: capファイルは普通にプロジェクトにあるし」「昔はCapistranoの設定方法がよくわからんという声もちょくちょくあったりしましたが、今もう聞かないっすね」

参考: Generators::Actions#capify!

config.ruの古い挙動のサポートを非推奨化

Rails 4以降で生成されるconfig.ruではデフォルトでRails.applicationのインスタンスが使われるようになってからだいぶ経ったことで非推奨化されました。

# railties/lib/rails/commands/server/server_command.rb#L21
-    # TODO: this is no longer required but we keep it for the moment to support older config.ru files.
     def app
       @app ||= begin
         app = super
+        if app.is_a?(Class)
+          ActiveSupport::Deprecation.warn(<<-MSG.squish)
+            Use `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0.
+            Please change `run #{app}` to `run Rails.application` in config.ru.
+          MSG
+        end
         app.respond_to?(:to_app) ? app.to_app : app
       end
     end

つっつきボイス: 「これもむかーしに、Rails起動時にアプリ名じゃなくてRails.applicationを指定できるように変わったので、その絡みでしょうね: Rails 4からだったかな」

後で探してみると、2013年の#9669↓がそれのようです。

# railties/lib/rails/generators/rails/app/templates/config.ru
 # This file is used by Rack-based servers to start the application.

 require ::File.expand_path('../config/environment',  FILE)
-run <%= app_const %>
+run Rails.application

rails runnerの引数で-をサポート

# railties/lib/rails/commands/runner/runner_command.rb#L15
       def self.banner(*)
-        "#{super} [<'Some.ruby(code)'> | <filename.rb>]"
+        "#{super} [<'Some.ruby(code)'> | <filename.rb> | -]"
       end

       def perform(code_or_file = nil, *command_argv)
# @@ -29,7 +29,9 @@ def perform(code_or_file = nil, *command_argv)

         ARGV.replace(command_argv)

-        if File.exist?(code_or_file)
+        if code_or_file == "-"
+          eval($stdin.read, binding, "stdin")
+        elsif File.exist?(code_or_file)
           $0 = code_or_file
           Kernel.load code_or_file
         else

つっつきボイス: 「ははぁ、-を指定することで標準入力が使えると: -自体は標準入力と標準出力のどっちにでも使われうるけどここでは標準入力ですね」

Relationに対するArelメソッド呼び出しが非推奨化

kamipoさんのPRです。

# activerecord/test/cases/relation/delegation_test.rb#L24
+  module DeprecatedArelDelegationTests
+    AREL_METHODS = [
+      :with, :orders, :froms, :project, :projections, :taken, :constraints, :exists, :locked, :where_sql,
+      :ast, :source, :join_sources, :to_dot, :bind_values, :create_insert, :create_true, :create_false
+    ]
+
+    def test_deprecate_arel_delegation
+      AREL_METHODS.each do |method|
+        assert_deprecated { target.public_send(method) }
+      end
+    end
+  end

つっつきボイス: 「今まではAR::Relationに対して直接arelを呼ぶと委譲されてたのが、今後は呼べなくなる方向になるってことか」「非推奨になるArelメソッドのexists↑は?なしの方なんですね: それなら直接呼べなくなってもいいかな」「fromsとかbind_valuesあたりは、ひょっとすると?使うことがあるかもしれないですが」「自分はArel直接呼ぶような事態になったら生SQL書くから別にいいや」

参考: exists?

「つまるところ、Arelは本当はprivateなAPIなんだぞってことを示してるんですかね」「Arelをどうしても使うならArelだけで組み立てて欲しい: AR::Relationで中途半端に組み立てたクエリをArelでいじるのとかやめて欲しいわー」「それで発生するバグつらそうですね…」「AR::Relationでスコープ変えたりするとまたわけわからなくなるし」

「kamipoさんのコミットメッセージ↓にこう書かれているから、relation.arel.#{method}はあっても基本使うなよってことですね」「privateなんだから代わりに呼ぶんじゃないよと」「もう禁止w」「それだけ事故が多かったからですかね」「というよりArel混ぜられると死ぬほど読みにくい!に尽きる」

I removed “Use relation.arel.#{method} instead.” in the message because it’s private API and fixed the next version of Rails to 6.0.
同PRより

ネストしたトランザクションの親のステートをベースにレコードのステートを適用

# active_record/connection_adapters/abstract/transaction.rb#L133
     class SavepointTransaction < Transaction
-      def initialize(connection, savepoint_name, options, *args)
+      def initialize(connection, savepoint_name, parent_transaction, options, *args)
         super(connection, options, *args)
+
+        parent_transaction.state.add_child(@state)
+
         if options[:isolation]
           raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
         end

つっつきボイス: 「ここまで頑張ってネステッドトランザクション書いたことないなー: そもそもどう処理されるか不安だし」「ただトランザクションって意図せずにネストすることがありうるから、最終的に挙動は知っておいたほうがいいけど」
「そもそも生SQLでもネステッドトランザクション書いたことないしw」「ストアドプロシージャとか、使い所はなくもないですけど、普通の業務で使うかなー?と私も思いますね」「できるのはわかるけど下手するとデッドロックしかねないし」

#0237da2のコミットメッセージを雑に訳してみました:


ネストしたトランザクションがあってレコードが両方でsaveされたとします。外側のトランザクションがcloseするとロールバックします。従来は、外側のトランザクションが「not persisted」とマーキングされていても内側のトランザクションが「persisted」になることがありました。

Post.transaction do
  post_one.save # ロールバックされる

  Post.transaction(requires_new: true) do
    post_two.save # 誤ってpersistedになる
  end

  raise ActiveRecord::Rollback
end

修正のため、このPRではトランザクションの扱いを変更して、子のトランザクションが親にマーキングの状態を確認するようにします。子トランザクションがあると、スタックが空でないため常にSavpointTransactionになります。その状態からparent_transactionを子のSavepointTransaction(親への子の追加はここで行われる)に渡すと、親は内側のトランザクションを「rolledback」とマーキングするのでレコードを「not persisted」とマーキングします。

update_attributes_from_transaction_stateでは、すべてのトランザクションがrolledbackとマーキングされ、内側のレコードがnot persistedと正しくマーキングされるよう、completed?チェックを用いる。

Post.transaction do
  post_one.save # ロールバックされる

  Post.transaction(requires_new: true) do
    post_two.save # 新しい振る舞いではnot persistedと正しくマーキングされる
    on rollback
  end

  raise ActiveRecord::Rollback
end

(同commitのテストを見ながら)「miniTestに#refute_predicateってありますね」「refute: 論破する、だから否定のアサーションか」

# activerecord/test/cases/transactions_test.rb#L372
...
+    refute_predicate topic_one, :persisted?
+    refute_predicate topic_two, :persisted?
+    refute_predicate topic_three, :persisted?
...

参考: refute_predicate

Rails

ActiveRecordをfixtureに変換する(RubyFlowより)

とても短い記事です。


つっつきボイス: 「おーこれあると便利なやつ: これは.to_yaml一発だけど他にもやり方は色々ある」
「マスターデータ的なやつならこんな感じでざっとfixtureにするのはラクでいいですよ」

「fixtureを使う場合問題になるのは外部キーで、ロードするときに面倒くさい」「あー順番に依存しているからか: それは面倒くさいっすね」「yamlってだけで読みづらいのに」
「その点FactoryBotで書けば順番とかよしなにやってくれますからね: fixtureだとそれがない」
「裏技的にMySQLの外部キー制約を一時的にオフにするオプションとかあるけど、そういうことをやり始めると知らないうちにfixtureがひっそりと壊れてたり」「なんやかんやで、fixtureで何でもやろうとするのは無理ありますね」

テスティングアンチパターン: セットアップしたデータの漏れ(Hacklinesより)

# 同記事より: 悪例
describe User do
  let(:user) { create(:user) }
  let(:account) { create(:account, user: user) }
  let(:role) { create(:role, user: user) }
  let(:post) { create(:post, user: user) }

  describe 'account-related stuff' do
    # tests go here
  end
  ... 
end

つっつきボイス:letを全部外側に置くとそのスコープ全部に効いちゃうからやめろよっていう、しごく普通のハナシ」「let嫌いーw: 遅延評価されるところとか」「お、let嫌いの同志を発見!」「let好き勢も社内にいますからねー」「letはキレイに使えるとかっこいいんだけど」「テストの内容によってはランダマイザーの影響とかで数回に1回とか数十回にコケたりすることもあるし」「途中でreloadする羽目になって面倒くさくなるし、reload使うと何だか負けな気がしたり」「基本letで定義したものは参照だけにしてテストの中で変えない方がいい

モデル内の翻訳する文章をJSON化する(Hacklinesより)


同記事より


つっつきボイス: 「これもローカライズ絡みということで」「まあ普通に行われていることですね: mangareborn.jpではyamlでやってたし」「データがでかくなると大変ですが」

loofah: nokogiriベースのHTML/XML操作/サニタイズgem

# 同リポジトリより
span2div = Loofah::Scrubber.new do |node|
  node.name = "div" if node.name == "span"
end

doc = Loofah.fragment("<h1>Title</h1><div>Content</div>")
doc.text    # => "TitleContent"           # probably not what you want
doc.to_text # => "\nTitle\n\nContent\n"   # better

つっつきボイス: 「ほー、HTMLを食わせて要素を置き換えたりto_textしたりサニタイズできる: XPathも使えるみたいだし、うんなかなかよさそうです」「ちなみにこのgemは脆弱性情報↓で知りました」「この種のパーサーはともすると脆弱性が潜むことがあるといえばありますからね」

JetBrains IDEはVMを設定しよう


つっつきボイス: 「JetBrainsにかぎらず、巨大IDEはEclipseでも何でもVMを適切に設定しておけばそう重くなったりしないですよ」

morimorihogeさんのRubyMineでは少なくとも以下ぐらいにVMを設定しているそうです。私のはデフォルトのままだった…

-Xms1024m
-Xmx8192m

ViewModelを分けることについて


つっつきボイス: 「TechRacho記事を引用いただいていたので見つけました」「ViewModelとPresenterを分けるっていう話のようですね」「ViewModelって?」「いわゆるMVVMパターン: JavaScriptの方でよく使われてる」「確かにPresenterが分かれている方がテストはしやすいですね」「正しいモデルを作りにくいけどビューの見た目だけチェックしたいときとか、ViewModelだけ差し替えられるようになってるとテストしやすい」「そういえばこの間の社内勉強会で近年のAndroidの動向を扱ったときもこの辺りの話が出ましたね」

参考: Wikipedia-ja MVVM

GitLabで重大なセキュリティリリース(Hacklinesより)


つっつきボイス: 「おっと、社内のGitLabもアップデートするか」

Ruby trunkより

ヒアドキュメントで改行をエスケープしたときの問題

puts <<~TXT.inspect
  1 \
  2
TXT

# 期待:
"1 2\n"

# 実際:
"1   2\n"

つっつきボイス: 「Rubyのヒアドキュメント記法って6つぐらいなかったっけ?覚えるの大変」「この<<~をSQLクエリで使うと先頭のスペースがなくなるからログが読みやすくなるって確かTechRachoの記事で見ましたよ」「(う、どこだったかな…)」「Rubyではこういうヒアドキュメントが言語仕様として用意されているあたり、ワカッテラッシャル」

参考: Rubyリファレンスマニュアル: ヒアドキュメント

誤った代入でsegfaultする->#14261で修正済みだった

# 同issueより
def foo
  puts 'hi'
end

foo, true  # 落ちる

つっつきボイス: 「むー、trueの前にカンマがあるからブロックと勘違いされたわけではなさそうだけど」「パーサーの気持ちになるのは難しす…」

CLI向けに文字が全角か半角かを取れるメソッドが欲しい

他の言語やgemにもある↓から言語にも取り入れて欲しいということのようです。

Python: unicodedata.east_asian_width (standard library)
https://docs.python.org/3.6/library/unicodedata.html#unicodedata.east_asian_width

Perl: "East_Asian_Width: *" of Unicode properties (regular expression in language)
https://perldoc.perl.org/perluniprops.html

Go: golang.org/x/text/width
https://godoc.org/golang.org/x/text/width

PHP: mb_strwidth (standard library)
http://php.net/manual/en/function.mb-strwidth.php

JavaScript: eastasianwidth (npm library)
https://www.npmjs.com/package/eastasianwidth

RubyGems: unicode-display_width gem
https://rubygems.org/gems/unicode-display_width

つっつきボイス: 「Unicodeの文字幅って全角と半角しかないんだっけ?」「んー、合字とかを別にすればそうだったかもしれないけど、どうだったかな…」

後で取り急ぎ以下を見つけました。

参考: [Ruby] Unicode 文字列の幅をそれなりにがんばって取得する - あおたくノート

プロポーショナルフォントの場合どうしようもないんだけど Unicode の文字は EastAsianWidth という仕様があって文字毎の幅がいわゆる全角か半角のどちらかになるかが決められている。
決められているといっても一部の文字は「Ambiguous(決定できない)」というふうに決められているのでそういう文字は自分で全角に倒すか半角に倒すか選ぶ必要はある。
同記事より

issueでも言及されているjanlelis/unicode-display_widthで「一応」取れるようです。

Ruby

「サーバーレスなRubyが欲しい」署名運動(Ruby Weeklyより)


つっつきボイス: 「サーバーレスRubyね…、あったらうれしいけどRailsが動くわけじゃないだろうし」「ここで署名している人の半分ぐらい、もしかしてRailsを動かしたいんじゃ?」「AWSのLamdbaでRubyがまともにサーバーレスで動くようになってごく軽いマイクロサービスとか建てられるようになったら確かにうれしい」「LambdaではRubyはまだちゃんと使えないんでしたっけ?」「このサイトのファビコンにLambdaのアイコンがあるぐらいだからまだっぽいですね」「確かまだだったと思う」「JRubyは動くみたいですけどJavaが動くから当然動くし」
「本格的なサーバーレスRubyはまだ難しいんじゃないですかね: gemはどこまで使える?とか、ActiveRecordのこの機能だけ使いたいとか」「ネイティブのgemぐらいなら使えるかもしれないけど」

参考: QuickStart Guide to Using the AWS SDK for Ruby - AWS SDK for Ruby

bundler-stats: gemの依存関係を調べるgem(Ruby Weeklyより)

# 同リポジトリより
> bundle-stats

+------------------------------|-----------------|-----------------+
| Name                         | Total Deps      | 1st Level Deps  |
+------------------------------|-----------------|-----------------+
... omitted stuff here ...
| fog                          | 15              | 6               |
| fancybox2-rails              | 15              | 1               |
| quiet_assets                 | 15              | 1               |
| coffee-rails                 | 18              | 2               |
| angular-rails-templates      | 19              | 3               |
| devise                       | 19              | 6               |
| rspec-rails                  | 20              | 7               |
| sass-rails                   | 21              | 4               |
| foundation-icons-sass-rails  | 22              | 2               |
| rails                        | 29              | 9               |
| angular_rails_csrf           | 30              | 1               |
| ngannotate-rails             | 31              | 2               |
| activeadmin                  | 48              | 12              |
+------------------------------|-----------------|-----------------+

Declared Gems:     35
Total Gems:        113

Unpinned Versions: 30
Github Refs:       1

つっつきボイス: 「こういう依存関係の情報取れるのはちょっとありがたいかも」

Ducalis: RuboCopベースの静的コードアナライザ(RubyFlowより)

# 同リポジトリより
ducalis --ci --repo="author/repo" --id=3575 --dry
ducalis --ci --repo="author/repo" --id=3575
ducalis --ci --adapter=circle # mode for running on CircleCI

参考: Wikipedia-ja 静的コード解析

もう何回言われたか覚えてない「Railsは死んだか: 2018年版」


つっつきボイス: 「2018年版ってのがウケる」「恒例の行事」

Ruby 2.6 preview 1

Noah Gibbsさんの記事とツイッターのやりとりです。

その他の記事/リポジトリ

SQL

pg_badplan: クエリプランと実際のクエリの乖離をチェック(Postgres Weeklyより)

-- 同記事より
\copy zip_codes from ~/src/create-statistics-talk/no_postal_codes_utf.csv with csv header;
COPY 4574

EXPLAIN (ANALYZE, TIMING off)
SELECT * FROM zip_codes WHERE city = 'Oslo' AND county = 'Oslo';
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Seq Scan on zip_codes  (cost=0.00..108.61 rows=90 width=36) (actual rows=642 loops=1)
   Filter: ((city = 'Oslo'::text) AND (county = 'Oslo'::text))
   Rows Removed by Filter: 3932
 Planning time: 0.357 ms
 Execution time: 0.679 ms
(5 rows)

データベース制約は最後の砦(Postgres Weeklyより)

CitusDataによる平易な解説記事です。

PostgreSQLで行の重複をサーチアンドデストロイする(Postgres Weeklyより)

-- 同記事より
SELECT id, firstname, lastname, startdate, position FROM
  (SELECT id, firstname, lastname, startdate, position,
     ROW_NUMBER() OVER 
(PARTITION BY (firstname, lastname) ORDER BY startdate DESC) rn
   FROM people
  ) tmp WHERE rn = 1;

JavaScript

JSプログラマーはどこで一番よく間違えるか(Frontend Weeklyより)


つっつきボイス: 「single biggest mistakeとある割に記事長いなー: 今読んでられない」「翻訳してみようかしら」

Cypress: フロントエンド向けテスティングフレームワーク


同リポジトリより

Railsでもbin/rake db:purge && cypress openで使えるそうです。


つっつきボイス: 「これはこれは、とってもCapybaraみたい↓」

describe("main navigation", () => {
  it("can navigate", () => {
    cy.visit("/");
    cy.get("[data-cy='nav-blog']").click();
    cy.location("pathname").should("eq", "/blog");

    cy.viewport("iphone-6");
    cy.get("[data-cy='nav-blog']").click();
    cy.location("pathname").should("eq", "/blog");
    cy.get("[data-cy='nav-about']").click();
    cy.location("pathname").should("eq", "/about");
  });
});

サードパーティJavaScript読み込みの影響(Frontend Weeklyより)


同記事より

Googleの解説記事です。

CSS/HTML/フロントエンド

CloudFlare Workers APIでJavaScriptを高速実行(JavaScript Weeklyより)

JavaScript向けの高速なCloudFlare Workers APIがでリリースされたという情報です。ドキュメントによるとHTTPトラフィックを扱う「Cloudflare Service Workers」とは別物だそうです。

センサーAPIがW3CでCRに(Frontend Focusより)


つっつきボイス: 「加速度計、ジャイロスコープ、磁気センサ、方位センサに間接光センサ…」

Wrapparizer: サイトで使われているソフトウェアを簡単に表示


つっつきボイス: 「既存システムがある案件の見積フェーズで有用そう」「Railsは50%で推測してますね」「JavaScriptの方をチェックしているのかも」

その他

Java 10がリリース


同記事より

参考: Java 10が本日付で正式リリース。ローカル変数の型推論、ガベージコレクタが入れ替え可能、不揮発性メモリ対応など。Java 9は早くもサポート期間終了 - Publickey

参考: Java 10新機能まとめ - Qiita

日本で働く外人エンジニア

読み物記事です。日本語混じりなので翻訳が逆に難しそう。


つっつきボイス: 「これはいろいろ面白いー」「忖度忖度」「『現在和訳中です』か…」「↓これもいいですね」

参考: How To Write Letters In Japanese

その手があった

番外

奥村先生が受賞

おめでとうございます。

塩粒以下のコンピュータ

アリさんに埋め込んだりできるでしょうか。


つっつきボイス: 「もう花粉症引き起こせますね」「こういうのが互いに相互連携して動くみたいなやつも出始めてるらしい」「体内で動くのはちょっと怖いかも」

参考: IBMが世界最小のコンピューターを発表、塩の粒より小さいサイズ - GIGAZINE

ブレインスキャナーがウェアラブルに

ポイントは「地磁気の影響をどうやって遮断するか」だったそうです。


今週は以上です。明日のRails Developers Meetup 2018でお会いしましょう。スライドがんばる…

バックナンバー(2018年度)

週刊Railsウォッチ(20180316)Rails 5.2のドキュメント更新中、Value Objectの使い方、RubyがTIOBEトップテン復活、Rails「雪だるま」エンコーディングほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Rails公式ニュース

Ruby Weekly

Awesome Ruby

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Postgres Weekly

postgres_weekly_banner

Frontend Weekly

frontendweekly_banner_captured

Frontend Focus

frontendfocus_banner_captured

JavaScript Weekly

javascriptweekly_logo_captured

JavaScript Live

jslive_logo_captured

JSer.info

jser.info_logo_captured

RailsのVue.jsをWebpackerとJestでテストする(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

RailsのVue.jsをWebpackerとJestでテストする(翻訳)

今やっているプロジェクトで、既存のRailsアプリにVue.jsを統合する方法の調査をを命じられました。とりあえず公式ガイドを読み、チュートリアルを視聴し、ブログを読みまくった結果、Vueコンポーネントを完全に動かせるようになりました。

最後の段階でいくつかテストを書こうとしましたが、残念なことにWebpacker gemにはテスト向け設定が含まれていなかったので、自分で設定せざるを得ませんでした。

驚いたことに、満足な設定方法がドキュメントに見当たらなかったのです。そこで私が何とかこれを動かせるようにしたときの方法を本記事で皆様と共有したいと考えました。

1. Jestのインストール

特にこれといった好みもなかったので、Jestを使うことに決めました。vue-cliも同梱されていたので使いました。

Jestのインストールで必要なのは、プロジェクトのルートディレクトリでyarn add --dev jestを実行することだけです。続いて、package.jsonに自分のテストスクリプトを追加します。

{
  "scripts": {
    "test": "jest",
    ...
  },
  ...
}

これで、yarn testを実行すればテストが走ります。

2. テストの置き場所を定義する

この時点でyarn testを実行しようとすると、config/webpack/test.jsでコケます。これは、Jestがプロジェクト内のテストファイルを探索する方法が原因です。基本的にはプロジェクト全体のうち.spec.js.test.jsにマッチするすべてのファイルを実行します。ここではテストファイルが*.test.jsとマッチしたので、テストとして実行されようとしていたのです。

自分たちのテストを実行する基準を満たすプロジェクト内の他のファイルと同様、Webpack configファイルを使わないようにしたいので、探索するファイルの場所をJestに指定する必要があります。

ここではRspecを使っていることもあり、spec/javascriptsディレクトリを探索場所として指定することにしました。もちろん、自分のプロジェクトに合う場所を自由に指定することもできます。

これを行うには、package.jsonファイルにrootsを追加しなければなりません。

"jest": {
  "roots": [
    "spec/javascript"
  ]
},

package.jsonのサイズがかなり大きいのでこのファイルにこれ以上設定を追加したくない場合は、--config <path/to/js|json>オプションでJest設定を定義できます。この方法にする場合、package.jsonは次のような感じになるはずです。

  {
    "scripts": {
      "test": "jest --config spec/javascript/jest.conf.js",
      ...
    },
    ...
  }

これが動作することを確認するために、以下のようなシンプルなテストをspec/javascript/team.spec.jsファイルを作成してもよいでしょう。

test('there is no I in team', () => {
  expect('team').not.toMatch(/I/);
});

これで再びyarn testを実行すれば、greenの「PASS」が表示され、動作していることがわかるはずです。

3. babelをrescueする

最初のテストが動くようになったので、一歩進めてVueコンポーネントをテストしてみましょう。

最初にやってみるのは、たいていspec/javascript/の下にファイルを作成してmy_component.spec.jsのような名前を付けることでしょう。続いて次のようにimportステートメントで自分のコンポーネントをspecにインポートしてみましょう。

  import MyComponent from '../../app/javascript/my_component.vue';

これをやってみた方は、そのままyarn testでテストを実行してみてください。SyntaxError: Unexpected token importエラーが出力されます。

ここでの問題は、importがECMAScript 6の一部である点です。つまりBabelなどのトランスパイラの助けが必要になるということです。

動くようにするには、yarn add --dev babel-jest babel-preset-es2015で2つのパッケージをインストールし、.babelrcファイルにes2015プリセットを追加する必要があります。

{
  "presets": ["es2015",
    ["env", {
          ...

さらに一歩進めてみたい方は、moduleDirectoriespackage.jsonに追加すると、モジュールへのフルパスを入力しなくても済むようになります。

"jest": {
  ...
  "moduleDirectories": [
    "node_modules",
    "app/javascript"
  ]
}

これで、先ほどのパスを簡単にできます。

  import MyComponent from '../../app/javascript/my_component.vue';

上のパスは以下のように書けます。

  import MyComponent from 'my_component.vue';

4. Vueが見つからない

ここまでの手順をすべて行った場合、yarn testでテストを実行するとまたしてもSyntaxErrorが表示されます。これは、コンポーネントのimportは成功したものの、.vueファイルフォーマットを理解できていないためです。

幸い、この辺りの面倒を見てくれるvue-jestを使えます。早速yarn add --dev vue-jestを実行してvue-jestをインストールし、READMEの指示に沿ってmoduleFileExtensionstransformmapCoverageを追加します。追加後のpackage.jsonは次のような感じになるはずです。

"jest": {
  ...
  "moduleFileExtensions": [
    "js",
    "json",
    "vue"
  ],
  "transform": {
    "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
    ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
  },
  "mapCoverage": true
}

moduleFileExtensionsを使うと、単一のファイルコンポーネントのimport.vue拡張子が不要になります。

  import MyComponent from 'my_component.vue';

上のパスは、以下のように書き換えられます。

  import MyComponent from 'my_component';.

これで、importをシームレスに使えるようになりました。

transformセクションに記述されたルールでは、テストするファイルの変換を担当するパッケージを指定します。ここではすべての.vueファイルの扱いをvue-jestに任せたいので、これらのファイルはJestで扱われる前に純粋なJavaScriptに変換されます。

mapCoverageは、トランスフォーマーが生成するソースマップを使うために設定してあります。Jestはこのソースマップを用いて、カバレッジの試行や、レポート生成時や閾値チェック中に元のソースコードとのマッピングを行います。

最後に、Vue.js公式の単体テストユーティリティライブラリであるvue-test-utilsを追加しましょう。yarn add --dev @vue/test-utilsを実行するだけですぐ使えるようになります。

ついにVueコンポーネントのテストを書けるようになりました🎉

クレジット

本記事は、Jest公式ドキュメントHow to setup JavaScript testing in Rails 5.1 with Webpacker and JestなどWeb上の多数のドキュメントから集めた情報を元に執筆いたしました。

関連記事

新しいRailsフロントエンド開発(1)Asset PipelineからWebpackへ(翻訳)

Vue.jsサンプルコード(35)入力すると自動的に次のフィールドにフォーカスを進める

$
0
0

35. 入力すると自動的に次のフィールドにフォーカスを進める

  • Vue.jsバージョン: 2.5.2
  • 日本の携帯電話番号の入力を想定しています(3桁、4桁、4桁)。
  • フィールドに入力すると、桁数いっぱいになったときに次のフィールドにフォーカスが自動で移動します。
  • 最後のフィールドは桁数いっぱいに入力してもフォーカスは移動しません。
  • Deleteキーは現在のフィールド内でのみ効きます(次回を参照)。
  • 画面をリロードすると最初に戻ります。

サンプルコード

ポイント

HTML側でv-modelとともにrefで名前を付けておくと、後でthis.$refs.r2などと参照できるので便利です。

      <div class="col-md-10 form-inline">
        <input class="form-control" size="3" v-model="a" ref="r1" />
          -
        <input class="form-control" size="4" v-model="b" ref="r2" />
          -
        <input class="form-control" size="4" v-model="c" ref="r3" />
      </div>
    watch: {
      a: function(v) { if (v.length >= 3) { this.$refs.r2.focus() } },
      b: function(v) { if (v.length >= 4) { this.$refs.r3.focus() } },
    },

バックナンバー(Vue.jsサンプルコード)

Vue.jsサンプルコード(01〜03)Hello World・簡単な導入方法・デバッグ・結果の表示とメモ化

Railsをバックエンドに持つReduxアプリを作ってみた(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

Railsをバックエンドに持つReduxアプリを作ってみた(翻訳)

ここ何か月もの間(大した期間ではないことはわかってますが)Flatiron Schoolでコーディングを学んでいましたが、同校のカリキュラムの最終プロジェクトで、私はReactとReduxをフロントエンドに、Ruby on Railsをバックエンドに使いました。

何しろ私にとって最初の大きなソロプロジェクトであり、しかもアプリ作成に使ったことのないReduxを使おうというのですから、相当頑張りましたが、間違いなく報われました。今やすっかりRedux信者です。

このアプリの目標は、自分の好きなテレビ番組をすべて保存して、放送時間やチャンネルを表示できるようにすることです。今は第二の「テレビ黄金期」ですので面白いテレビ番組が溢れかえっていますが、とても全部は追いきれません。番組の放送時間/チャンネル/系列ネットワーク/Webサイトやらなんやら、今週の新番組なのかどうかなどを忘れないようにするのは無理です。そこに登場するのが私のMyLineupアプリです。

デモ版アプリはこちらでご覧いただけます。デモ版はログイン不要で、データも入っていますので、ユーザー(そう、皆さんです)が簡単にアプリの機能を知ることができます。アプリのREADMEファイルはGitHubリポジトリでご覧いただけます。

バックエンド

アプリのバックエンドはRuby on Railsで作成しました(GitHubリポジトリ)。バックエンドの責務はデータベースがらみのクエリだけなので、セットアップはかなり簡単でした。また、TVデータの取得に2種類の異なるAPI(trakt.tv APITVmaze API)を使いました。パフォーマンス監視については、NewRelicでバックエンドを最適化しました。

作ったモデルは、UserShowEpisodeUserShowUserEpisodeです。

  • Userはだいたいこうなるだろうという感じです。
class User < ApplicationRecord
  has_many :user_shows
  has_many :shows, through: :user_shows
  has_many :user_episodes
  has_many :episodes, through: :user_episodes
  has_secure_password
end
  • Show
class Show < ApplicationRecord
  has_many :user_shows, dependent: :destroy
  has_many :users, through: :user_shows
  has_many :episodes
end
  • Episode
class Episode < ApplicationRecord
  has_many :user_episodes, dependent: :destroy
  has_many :users, through: :user_episodes
  belongs_to :show
end

UserShowUserEpisodeはそれぞれ、UserShowのjoinテーブルと、UserEpisodeのjoinテーブルです。

class UserShow < ApplicationRecord
  belongs_to :user
  belongs_to :show
end
class UserEpisode < ApplicationRecord
  belongs_to :user
  belongs_to :episode
end

作成したモデルでは、ユーザーが番組を評価できます。また、ユーザーが番組やエピソードを削除したときにデータベースから削除しないようになっています。

モデルごとに、ユーザーのCRUD操作のためのコントローラも必要です。Showsコントローラは、表示のCRUDアクションの他に、外部APIへのリクエストのフェッチCRUDアクションも扱うので、責務は最も大きくなりました。

フロントエンド

上述のとおり、私はフロントエンドを(自分がよく知っている)Reactと、(学び始めて数日しか経っていない)Reduxで作りました。Reactのコンポーネントやステートの構造は快適でしたが、Reduxではまごついてしまいました。しかし私は挑戦せずにはいられない男なので、Reduxを試してみたかったのです。そしてReduxは私にとってかなりよかったことを申し添えなくてはなりません(フロントエンドのリポジトリはこちら)。パフォーマンスの監視にはChrome Performance Dev Toolsを使いました。

ダッシュボード

ログインすると、ユーザーは自分の「Dashboard」にリダイレクトされます。Dashboardには、その日の夜の「ラインナップ」や、その日の夜のおすすめ番組が表示されます。

「My Lineup」に表示されるのは、Reduxストアの「myLineup」から取って来た、その日に放映されるエピソードです。「Other Shows to Watch」には、TVmaze APIから取ってきた情報(その日に放映されるあらゆる番組を取れます)を表示します。リストを使いやすくするために、レーティングが8を超え、かつ「User’s Lineup」にまだない番組だけをフィルタで表示しました。

this.props.onTonight.filter(episode => episode.show.rating.average > 8 && !ids.includes(episode.id))

「Dashboard」を含むコンポーネントをステートフルにしなければなりませんでした。これはReduxの「真の単一情報源」とは逆を行くものですが、よいきっかけでした。これによって結果をページネーションしてユーザーがエピソードのリストをスクロールできるようになりました。フィルタされた結果を分割して、表示するステートに5つずつ保存しました。「Older」をクリックすると、フィルタされた結果に直前の5つの項目を単に表示します。このあたりを手伝ってもらったLindsey Wellsへのリンクを貼っておきます。

「My Lineup」

このプロジェクトで(少なくとも私にとっては)最も重要な部分は、言うまでもなく、ユーザーのラインナップを表示するカレンダーです。番組がいつどの局で放映されるかをカレンダーで正確に知ることができます(サンプルアプリ)。

ここにある番組を片っ端から見る時間があったらよいのにと思います。

ユーザーの番組をすべて表示するのに使ったのはReact Big Calendarです。RBCの使い勝手はそれほどよくありませんが、これを使ってカレンダーのラインナップにユーザーのエピソードをすべて表示することに成功しました。時刻のフォーマットや設定にはMoment.jsを使いました。

ユーザーは、その日の番組や一週間分の番組を知ることができます。カレンダーを進めたり遡ったりもできます。保存した来月分の全番組の概要も表示できます(そこまでして予定を押さえておきたい場合に備えて)。

ラインナップからエピソードを削除することも、エピソードの詳細をモーダルポップアップに表示することもできます。

ページの最下部にはその夜のおすすめ番組が表示され、番組をクリックするとモーダルポップアップでラインアップに直接追加できます。

プレミアカレンダー

Premieresカレンダーは、その週のプレミア(シリーズの最初のエピソード)をすべて表示できるお楽しみ機能です。

これも同様に、1日分または1週間分を表示できます。trakt.tv API から取得した新作番組をフィルタして、レーティングの高い番組だけを表示しています。さらに、「エピソード1」や「シーズン1、エピソード1」の違いがわかるようにして、その番組がTotal Divasのようにシーズンの続きなのか、S.W.A.T.のようにまったくの第1回なのかがわかるようにしました。

これもモーダルポップアップで追加したり番組情報を表示したりできます。

トレンド/高視聴率

これはtrakt.tv APIの楽しい機能です。番組のその瞬間のトレンド(APIでは1時間おきに更新)や、最も視聴率の高い番組を表示でき、時間でフィルタすることもできます。

このユーザーインターフェイスにはSemantic UIを使いました。

検索

検索では、TVmaze APIを呼び出して結果を表示します。表示された番組を追加して番組リストにリダイレクトできます。

ShowsページとShowページ

最後はShowsページとShowページです。Showsページにはユーザーが保存したTV番組がすべて表示されます。ユーザーが番組をクリックすると、番組の個別のページにリダイレクトされます。

個別のページには、ユーザーが選択した番組の情報と、番組の全エピソード(上述のページネーション機能を利用)、およびその番組に関連するおすすめ番組が表示されます。

おすすめ番組はtrakt.tv APIから取り出します。ユーザーがまだ視聴していない番組だけを表示します。

この星はアプリのユーザーが付けたものです。それ以外の情報はすべてTVmaze APIからのものです。

アプリの紹介は以上です。アプリ作成はとても楽しく、今もいくつかの機能を拡張してみたいと思っているほどです。特に、同じ番組を保存した他のユーザーをEpisodeページで表示できるようにしてSNS的な要素を加えたいと思います。今後どうなるかはいずれわかるでしょう。

関連記事

JavaScript: Reduxが必要なとき/不要なとき(翻訳)

Reduxストアの概念をRubyで再実装して理解する(翻訳)

Viewing all 164 articles
Browse latest View live