『DNSをはじめよう』を読む

参考

DNSをはじめよう

route53

route53はAWSの提供するDNSのサービス。ドメインのゾーンを作成できる。

ちなみにec2は Elastic Compute Cloud の略。AWSではサーバのことをインスタンスと呼ぶ。

whois / dig

whois とはドメインの管理の状況や所有者の情報を確認できるサービス。

レジストリによって表示される項目が少しづつ違う。

ターミナルから whois コマンドを使うと、 Whois 情報を調べることができる。 whois コマンドではドメインの持ち主の情報だけでなく、IPアドレスの持ち主の持ち主の情報も調べることができる。

whois IPアドレス

digコマンドでもドメインからその情報を取得できる。

dig ドメイン名 [レコードのオプション] +short

であればそのドメイン名にひもづくIPアドレスを取得することができる。

+short は結果だけを表示させるオプションで、つけなければ付加情報を含んだ結果が返ってくる。

a オプション

a はデフォルトのオプションでたぶんadressの略?

mx オプション

mxはメールの情報。mはたぶんmail。

メールアドレスから実際にどのサーバにメールが送られているのかを紐づけるレコード。これが設定されていない場合はaに紐づけられたサーバにメールが送られる。

ns オプション

nsはネームサーバ。name_server。

ネームサーバを確認することができる。ネームサーバはAWSでいうとroute53で設定できる。

txtオプション

そのドメインのメール送信者の情報が定められてる。例えば、そのドメインが送信者として保証するIPアドレスもそこに記述されてて、もしそのIPアドレス以外からそのドメイン名でメールが届いて入ればそれが不正である可能性が高いとして迷惑メールとして認識されるようになる。

これらはそれぞれのレコードの存在を前提に検索できる。(aならaレコードがあれば検索できる。a レコードにそのドメインIPアドレスの関係が記録されている。)

-xオプション

これはPTR レコードを調べる。PTR レコードはIPアドレスからドメインを逆引きするためのもの。その他のレコードはドメインのネームサーバ側から設定することができるが、このレコードはIPアドレスを持っているサーバ側からでないと設定できない。

dig -x IP アドレス +short

MVCについて

よく見るMVCの図

いつものmvc
いつものmvc
もちろんこれは筋が通ってる。

①ブラウザとかのクライアントサイドからアプリへリクエストを送る。

②それを routes.rb が解釈して該当のコントローラとアクションを探す。

③コントローラはHTTPリクエストのパラメータを受け取ってアクションを実行する。その際必要に応じてモデルにアクセスし、データを渡したり受け取ったりする。

④モデルはDBにアクセスして、各自メソッドを実行する。

⑤コントローラはモデルとのやりとりを通してCRUDなどの処理を実行し、自分のviewを呼び出して、結果の値をインスタンス変数に入れて渡す。

⑥viewがコンパイルされて、htmlとしてブラウザに表示される。

僕がイメージするMVC

私のイメージ
私のイメージ
コントローラの中にモデルが点在していて、それぞれDBにアクセスし、コントローラの任務を実行・手助けする。コントローラ名とそこにいるモデルは必ずしも結びついていない。会社に例えるとコントローラは部署で、その中にアクションという業務がある(アクションは基本的にCRUDのみ)。

インスタンスに仕事を覚えてもらう

社員にあたるのがモデルのインスタンス。社員の行動は社内規定にあたるモデルのクラス定義の制約を受けている。クラス定義にモデルにやってもらいたい仕事を書いておけば、便利な仕事のスキルを社員に予め仕込んでおくことができる。そうすれば、コントローラ内で指示すべきことが減り、インスタンスに「アレやっといて」というだけでキチンと業務をこなしてくれる。つまり、優秀な社員が手に入る。結果的にコントローラが薄くなって混乱が起こりにくくなる。

コントローラの処理もまとめる

ただし、社員に仕込むスキルは彼自身(データ)に結びつくものであるべきで、そうでない処理についてはコントローラ内にプライベートメソッドとしてまとめる。別の部署でも共通の業務であれば ApplicationController にまとめておく。

カンバン方式で生産性向上を図る

カンバン作業の様子

前書き

生産性を上げるためにできることについて考えてみました。今はやっていますが、自分の場合ほっておくと飽きてやらなくなることが目に見えているので未来の自分に宛てて記事にしておきます。

進捗が可視化されてモチベーションを保ちやすい。

プロダクトが形になってくると楽しいですが、目に見えなくて実感が湧きにくい実装というのもあります。タスクを付箋で管理すると結果が可視化されて、やる気が持続しやすくなります。苦労した分前に進んでいるということを自分自身に伝えることも大切だと思います。

タスクの細分化

またこれに関連して、なるべくタスクを細分化して付箋の枚数を増やすことも生産性を上げる上で有効だと思います。付箋一枚を消化するサイクルが早くなると上記で述べたようにやる気が続きやすくなります。またタスクは抽象的であるほど、着手しづらくなるため作業の流れを止める原因になってしまいます。抽象的な課題が思いついたら(「UIを改善する」など)一旦それをそのまま付箋に書いて、あとで複数枚の付箋にバラすのが良いと思います。

スループットを意識しやすくなる。

「コードを書いた時間」や「インプットした時間」がプロダクトを作る時のスループットではない、ということを意識しやすくなります。もちろんインプットしたり、その時の作業のために最適なやり方を探すのはスループットのために重要なことではあるし、試行錯誤することも必要な作業ではあるとは思いますが、往々にして手段は目的と入れ替わり、それに気づかず、肝心のスループットである機能の実装がなされていないのに、充実感だけが得られてしまう、ということが僕の場合かなり頻繁に起こります。当然付箋のタスクは消化されていないので、それがフィードバックになって軌道修正ができます。

今取り組んでいる作業に集中しやすくなる。

タスクを書き出しておくことにはそれ自体に意味があると思います。思いついたやるべきことを付箋に書いておけばとりあえずは頭からそれを出しておくことができるのでやり残したことが気になって集中力が落ちる、ということも起こりにくくなると思います。また、やり残したタスクが目に見えないとやってもやっても終わらないという感覚になり、モチベーションを奪うので付箋の形にするのは有効だと思います。

参考にしたもの

前に読んだ本の考え方を参考にしました。

ザ・ゴール

GTD

i18nの利用

何がしたいか

例えばenumを使うときにはキーとなる文字列をviewで表示したいけど、そのままでは英語表記になってしまう。その場で変換するコードを書いたんではenumの意味がない。そういうときに予め辞書ファイルを作っておいて変換できれば便利。i18nは本来アプリの国際化の対応のための仕組みだと思うけど、今回はプログラム→UIの対応のために利用。

辞書ファイル

my_app/config/locales/ja.yml

のようにja.ymlファイルを作成。

ja:
  school:
    category: 
      general: '普通'
      agricultural: '農業'
      industrial: '工業'
      commercial: '商業'
      fishery: '水産'
      home_economy: '家庭科'
      nursing: '看護'
      information: '情報通信'
      welfare: '福祉'
      other: 'その他'

アプリの設定

下記のファイルを編集

my_app/config/application.rb

このファイルはサーバーを起動するときにしか読まれないようなので、書き換えたら再起動しないと反映されない。

module My_app
  class Application < Rails::Application
    ~略~
    config.i18n.default_locale = :ja
    ~略~      
  end
end

viewでの活用

<%= t'school.category.general' %>

上のようにすれば 「普通」が描画される。

辞書ファイルはヘルパーメソッド t から呼び出すことができる。

<%= t("school.category.#{@school.category}") %>

上のようにすれば動的に辞書を利用することができる。

一つのフォームから複数のモデルを保存

何がしたいのか?

belongs_to/has_manyの関係を持つ2つのモデルのインスタンスをbelongs_to側のformから同時に作成したい。

view

form.fields_for のようにして params を入れ子にすることは避けた。理由はリレーションの親子関係が逆になってややこしくなると思ったため。今回は fields_for @school にして別のパラメータとしてコントローラに送るようにした。

<%= form_with(model: teacher, local: true) do |form| %>

    中略

  <div class="field">
    <%= form.label :name, '名前' %>
    <%= form.text_field :name %>
  </div>

    中略

  <%= fields_for @school do |school_form| %>
    <div class="field">
      <%= school_form.label :name, '学校名' %>
      <%= school_form.text_field :name %>
    </div>
  <% end %>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

コントローラ

belongs_to のリレーションがあると自動的にその属性で reqire :true になる。また、build_〇〇 というメソッドが使えるようになる。

@school = @teacher.build_school(school_params)
@school.save

とすることで @school の生成と @teacher の school_id の取得を同時に行うことができた。

class TeachersController < ApplicationController
  def new
    @teacher = Teacher.new
    @school  = School.new
  end
    
  def create
    @teacher = Teacher.new(teacher_params)
    create_school
    if @teacher.save
      reset_session
      session[:teacher] = @teacher.id
      redirect_to root_path
    else
      redirect_to root_path
    end
  end

  private
    def create_school
      @school = @teacher.build_school(school_params)
      unless @school.save
        redirect_to root_path
      end
    end

    def teacher_params
      params.require(:teacher).permit(:school_id, :name, :password, :password_confirmation, :email)
    end

    def school_params
      params.require(:school).permit(:name)
    end
end

感想

コントローラに別のモデルを保存する処理を書いて良いものか少し迷った。TeachersControllerにSchoolsControllerを継承するという手もあるけどそれはそれで微妙な気がしたのでやめた。

追記

今回のパターンだと accepts_nested_attributes_for を使った方が良さそうです。下記のページでまとめました。

accepts_nested_attributes_forの使い方

google map API でrailsアプリにmapを描画

前書き

rails でモデルが持っている住所情報からgoogleマップを表示するjsを書いたときに詰まったのでメモ。

参考

地名や住所から緯度・経度を取得する

内容

google map api では緯度経度からmapを描画することになっている。しかしながらモデルが持っている情報は住所である。ググったところ、Geocoderというgemについての記事がたくさん見つかったので使ってみた。どうやらこのライブラリは内部に住所や施設の名前などから緯度経度までいろんな情報を持っている優秀なgemだということがわかった。しかしながら、例えば私の出身地である静岡県湖西市などの都会ではない場所の情報は持っておらず、mapが表示できない問題があることがわかった。gemに地図の情報がたくさんあるのはすごいのだけど、なんでgemに頼る必要があるのか?一番地図情報を持ってるにはgoogle mapに決まってるよね。と考え直してさらにググったところ、上記の参考サイトを見つけてやっぱりgoogle map自体もGeocoderというクラスを持っていることがわかった。そして下記のように実装。うまく描画された。

<div id="map"></div>

<script>
function initMap(latlng) {
  var map = new google.maps.Map(document.getElementById('map'), {
    center: latlng,
    zoom: 16
  });

  var marker = new google.maps.Marker({
    position: latlng,
    map: map
  });
}

function getLatLng() {
  var geocoder = new google.maps.Geocoder();

  geocoder.geocode({
    address: "<%= @place.prefecture %><%= @place.city %><%= @place.street %>"
  }, function(results, status) {
    if (status == google.maps.GeocoderStatus.OK) {
      for (var i in results) {
        if (results[i].geometry) {
          var latlng = results[i].geometry.location;
          initMap(latlng)
        }
      }
    }
  });
}
</script>

<script src="https://maps.googleapis.com/maps/api/js?key=[APIのキー]&callback=getLatLng"
async defer></script>

結論

google map apiを利用するときにはgemではなくgoogle.maps.Geocoderクラスを利用するのが良い。

その他

mapのサイズ等cssをscssファイルに書くのを忘れないように。

Scrapy Item Pipeline

参考資料

Item Pipeline — Scrapy 1.5.1 documentation

バージョン

  • Python 3.6.5

  • Scrapy 1.5.1

  • pymongo 3.7.2

概要

spiderによって取得したアイテムは Item Pipelineへ送られる。 Item Pipelineには主に4つの役割がある。

  • HTMLデータを整理する
  • データのvalidationを行う
  • データにダブりがないか確認して、あれば弾く。
  • DBにアイテムを保存する。

pipelineクラスは四つのメソッドによって実装する。

process_item(self, item, spider)

このメソッドは全てのpipelineコンポーネントで呼び出される。

このメソッドはスパイダーが取得したitemをdict型なり、下位のオブジェクトなりデータベースに適した形に整形する役割を持っている。

    open_spider(self, spider)

このメソッドはspiderが開始されたときに呼び出される。

close_spider(self, spider)

このメソッドはspiderが終了したときに呼び出される。

from_crawler(cls, crawler)

このメソッドが宣言されている場合はcrawler本体からpipelineクラスを呼び出し、そのインスタンスを作ることができる。これがpipelineとその他のコンポーネントとの通路のような役割を果たす。

具体例

下記はmongoDBと接続する時の例。

# 1
import pymongo

class MongoPipeline(object):
    # 2
    collection_name = 'scrapy_items'

    # 3
    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
    # 4
    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
        )
    # 5
    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]
    # 6
    def close_spider(self, spider):
        self.client.close()
    # 7
    def process_item(self, item, spider):
        self.db[self.collection_name].insert_one(dict(item))
        return item
  1. pymongoを利用してmongoDBと接続する。
  2. collection_nameはitemクラスの名前がつけられる。
  3. 初期化メソッドnewで呼ばれる。3で説明。
  4. from_crawlerメソッド。crawler本体で呼ばれるメソッド。ここでこのクラスのインスタンスが作られる。内部的にsettingsファイルから変数MONGO_URIとMONGO_DATABASEを取ってきてそれを2番のようにアクセサメソッドとして格納。
  5. 2/3の設定を使ってmongoDBに入場。
  6. mongoDBから退場。
  7. process_itemメソッド。ここでmongoDBにitemがうまく入るように整形。dbとその中のitemクラスを指定して引数のitemを辞書型に変換してinsertしている。

その他

activating-an-item-pipeline-component (重要)

これを設定しないとコンポーネント自体が動かない。

Item Pipeline — Scrapy 1.5.1 documentation

pythonインスタンスメソッドとクラスメソッド

@classmethodとは?第一引数がselfのメソッドはインスタンスメソッド。インスタンスメソッドはその名の通り作られたインスタンスに持たせるメソッド。クラスメソッドはクラスが持っているメソッド。ここではmongo_uriとmongo_dbの二つのアクセサメソッドをもつクラスを返すメソッドが定義されている。

pymongo

Installing / Upgrading — PyMongo 3.7.2 documentation

pymongo-3.7.2をインストール。インストール時のメッセージ特になし。