MogLog

メモというか日記というか備忘録というか

【Rails】データベース名を明示せずにActiveRecordでMySQLに接続する

環境

状況

データベースはまだ存在しないが、ActiveRecordMySQLへの接続だけは確立したい。

何も考えずに次のようなコードを実行すると...

ApplicationRecord.connection.execute("<なんかSQL>")

次のようなエラーが起こる。

ActiveRecord::NoDatabaseError: Unknown database '<データベース名>'

どうするか

このような場合は、次のようにすると比較的スッキリ書ける。

config = ApplicationRecord.connection_config
ApplicationRecord.establish_connection(config.merge(database: nil)) # ★
ApplicationRecord.connection.execute("<なんかSQL>")

こんなんいつ役に立つんじゃい

自分は以下のような状況で使った。

  • 一時的に複数のデータベースを扱う必要があるRailsアプリケーションがある(そのためdatabase.ymlが2つある状況)
  • 2つ目のデータベースの作成と削除も、rakeタスクでRailsらしく実行したい
    • bin/rails 2nd_db:createとかそんな感じで
  • 2つ目のデータベース用の基底クラス(ActiveRecord::Baseを継承したクラス)があるので、この基底クラスから CREATE DATABASE <2つ目のデータベース> を発行したい。だが単純に実行しようとすると先述の通りエラーになる。
  • 実装の都合上、1つ目のデータベースに接続するApplicationRecordはできれば使いたくない
  • そこでコレ

状況がニッチすぎて誰得な感じは否めないが、自分はそれなりに悩んだのでインターネットの海に放流しておこうと思った次第である。その他、エレガントな実装方法があれば教えてほしい。

【Rails】app/以下に新規作成したディレクトリのファイルが読み込まれない件

結論:springの再起動が必要だった

あらまし

Ruby On Railsアプリケーションの開発中、app/以下に新しくvalidatorsというディレクトリを作ることになった。 最近のRailsではapp以下のファイルを自動で読み込んでくれるようになっているので、追加の設定は不要だ。

Railsガイドにも次のように書かれている。

ところで、Railsにはpost.rbのようなファイルを探索する$LOAD_PATHに似た、ディレクトリのコレクションがあります。このコレクションはautoload_pathsと呼ばれており、デフォルトで以下が含まれます。

アプリケーションとエンジンのappディレクトリ以下にあるすべてのサブディレクトリ。app/controllersなどが対象。app以下に置かれるapp/workersなどのカスタムディレクトリはすべてautoload_pathsに自動的に属するので、デフォルトのディレクトリとする必要はない。

定数の自動読み込みと再読み込み | Rails ガイド

そう、そのはずなのだが何故か読み込まれない。 bin/rails cでコンソールを立ち上げて、$LOAD_PATHActiveSupport::Dependencies.autoload_pathsを確認してもやはりapp/validatorsは含まれていない。

1時間くらいハマッて調べた結果、springがキャッシュを持っていることが原因だとわかった。

$ bin/spring stop

上のコマンドでspringを停止し、その後 bin/rails c でコンソールを立ち上げて確認したところ無事に読み込まれていた。 springは裏でひっそりと動くのであまり気にしなくて良い反面、存在自体を忘れがちである。

draperを使ってみた

draper というデコレーター(またはプレゼンター)の機能を持つGemを使ってみた記録。

draperとは

  • draper is ...
    • ViewModel for Rails
    • Railsアプリケーションにオブジェクト指向なプレゼンテーション層を追加する
    • Modelにメンテナンス性の高いプレゼンテーションロジックを包み込む

なぜdraperを使うのか

  • Viewにまつわるロジックの置き場としてHelperも存在する。しかし、Helperはグローバルスコープを持っているため全てのControllerとViewで利用できてしまう
  • Helperを使った場合、Modelに応じた微妙な差異を吸収するために新しいメソッドが追加される
    • 例:Articleモデルは日時をYYYY-MM-DD HH:mm:ssで表現したいが、BookモデルはYYYY-MM-DDで表現したい
  • Helperはオブジェクト指向に書きづらい

→ draperはこのような問題を解決する

使ってみる

セットアップ

gem 'draper'をGemfileに追記してインストールしたら、ジェネレートコマンドを使って一式生成する。
Userモデルがあるとして、それに対応するdecoratorを生成する。

>> bin/rails g decorator User
      create  app/decorators/user_decorator.rb
      invoke  test_unit
      create    test/decorators/user_decorator_test.rb

app以下にdecoratorsディレクトリが作成され、その下にuser_decorator.rbが追加された。

デコレートメソッドを追加する

早速Userモデルをデコレートしていく。
ここでは、Userが自己紹介をするためのデコレートメソッドを追加した。

# app/decorators/user_decorator.rb
def greeting
  "Hi! My name is #{user.name}. I am #{user.age} years old. Thank you."
end

decoratorクラス内で、対応するモデルにアクセスするには object か 対応するモデル名(Userモデルの場合は user)を呼び出す。

Controllerでモデルをデコレートする

Modelをデコレートする準備は整ったので、次はControllerで取得したModelをデコレートした上でViewに受け渡す。
次のように、Modelに対して decorate メソッドを呼び出すだけでOK。

# app/controllers/users_controller.rb
def show
  @user = User.find_by(id: params[:id]).decorate
end

Viewでデコレートメソッドを呼び出す

Viewではデコレートされていることを意識する必要はなく、普通にメソッドを呼び出すだけでいい。

<!-- app/views/users/show.html.erb -->
<h1><%= @user.greeting %></h1>

画面

f:id:mogulla3:20180210122304p:plain

これでdraperを使った一連の実装を体感できた。

アソシエーションに対応する

関連するモデルも含めてデコレートしたい場合は、次のようにデコレータクラスで decorates_association メソッドを呼び出す。

# app/decorators/user_decorator.rb
class UserDecorator < Draper::Decorator
  # ここでは、UserはN個のDeckを持つ関連が設定されているとする
  decorates_association :decks
end

これで、deck側もデコレートされたオブジェクトとして扱うことができる

その他のメモ

  • decoratorクラス内で、helperにアクセスするには hを呼び出す
    • 例:h.content_tag(:strong, user.name)
  • コレクションをdecorateするいくつかの方法があるようだ
    • UserDecorator.decorate_collection(User.all)
    • User.all.decorate
    • これは具体的に何が違うのかまで調べていない
  • ApplicationDecoratorなどとして、Decorator共通の処理を括りだすことができる。この場合、各デコレータクラスはDraper::DecoratorではなくApplicationDecoratorを継承するようにする
    • class ApplicationDecorator < Draper::Decorator
    • class UserDecorator < ApplicationDecorator
  • Decoratorクラスはデフォルトでdeltega_allが呼び出され、全てのメソッド呼び出しを委譲しているが、delgateメソッドで個別に指定することも可能
  • どのデコレータを使うかの指定も可能。デフォルトではファイル名から判定されている。

ActiveModel::Serializersを使ってみた

ActiveModel::Serializers(AMS) という、出力するJSONをいい感じにコントロールできるというライブラリを使ってみた記録。多分、RailsAPIを作るときなんかによく使われるライブラリ、くらいの認識からスタートした。

github.com

出鼻をくじかれる

「よーし使ってみるか」という気持ちで意気揚々と調べ始めたところ、2つの点で出鼻をくじかれた。

README.mdを読んだ

masterブランチの README.md を見ると、ざっくり次のようなことが書かれていた。

  • masterブランチへのPRはcloseやめて、0-10-stableブランチへ頼むよ
  • 0.10.xバージョンは巨大なメンテナンスバージョンになっているよ
  • 0.10.xバージョンは安定版だけど、積極的なメンテナンスはしていないよ
  • バージョン1.0に向けての開発はしているよ

要するに、現在の安定バージョンは 0.10.x であるが積極的なメンテナンスはされていないし、PRも基本的に受け付けていない。今は、色々あって1.0に向けて大規模な改修(開発)をしています...、といった感じ。

fast_jsonapiの登場

NetFlix社から、fast_jsonapiというライブラリを開発したというブログが公開された medium.com

これは ActiveModel::Serializers が提供する多くの機能を受け継ぎつつ、パフォーマンスを改善したライブラリらしい。AMS(ActiveModel::Serializersの略称)と比較して、パフォーマンスは25倍近く向上したと書かれている...。

...といった感じで出鼻をくじかれてしまった。
しかしAMSは有名なライブラリ(のよう)だし、fast_jsonapiはAMSに影響を受けているようなので、まぁやって無駄にはならないだろうと考えて当初の予定通り使ってみることにした。

Getting Started

まずは動かすところまでやってみる。ガイドを参考に進める。

まずは、DBスキーマ、モデル、コントローラを適当に一式用意する。

DBスキーマ

create_table "users", force: :cascade do |t|
  t.string "name"
  t.integer "age"
  t.string "sex"
  t.boolean "premium"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["name"], name: "index_users_on_name", unique: true
end

モデル

# app/models/user.rb
class User < ApplicationRecord
end

コントローラ

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    render json: User.all
  end

  def show
    render json: User.find_by(id: params[:user_id])
  end
end

ルーティング

# config/routes.rb
Rails.application.routes.draw do
  get 'users' => 'users#index'
  get 'users/:user_id' => 'users#show'
end

ここまでで、サーバを起動してリクエストを送るとJSON形式でuserデータを返してくれる状態になった(※ マイグレーションやテストデータの挿入は省略している)。

>> curl -s localhost:3000/users/1 | jq .
{
  "id": 1,
  "name": "user-0",
  "age": 1,
  "sex": "male",
  "premium": true,
  "created_at": "2018-02-03T01:59:54.243Z",
  "updated_at": "2018-02-03T01:59:54.243Z"
}

ここからAMSを使ってみる。まずはシリアライザクラスを作る。

bin/rails g serializers User

これで、app/serializers/user_serializers.rbが作られる。ファイルの中身は次のようになっている。

# app/serializers/user_serializers.rb
class UserSerializer < ActiveModel::Serializer
  attributes :id
end

attributesメソッドに指定した値だけをレスポンスするようになるので、この状態で再びリクエストを送ると次のような結果になる。

>> curl -s localhost:3000/users/1 | jq .
{
  "id": 1
}

attributesメソッドにidしか指定してないので、レスポンスに含まれるデータもidのみとなった。 attributesに複数の引数を指定することももちろん可能。

# app/serializers/user_serializers/user_serializers.rb
class UserSerializer < ActiveModel::Serializer
  attributes :id, :name, :age
end

レスポンスも合わせて変化する。

>> curl -s localhost:3000/users/1 | jq .
{
  "id": 1,
  "name": "user-0",
  "age": 1
}

ここまでで、とりあえずAMSがどんなものかのイメージができた。

アソシエーションに対応させる

次は、モデルのアソシエーションに対応させてみる。 Userは0個以上のDeckを持てるものとして、データモデルを変更する。

DBスキーマ

create_table "decks", force: :cascade do |t|
  t.string "name"
  t.string "description"
  t.string "image_url"
  t.boolean "favorite"
  t.integer "user_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["user_id"], name: "index_decks_on_user_id"
end

モデル

# app/models/user.rb
class User < ApplicationRecord
  has_many :decks
end

# app/models/deck.rb
class Deck < ApplicationRecord
  belongs_to :user
end

リアライザ

# app/serializers/user_serializer.rb 
class UserSerializer < ActiveModel::Serializer
  attributes :id, :name, :age
  has_many :decks # ★ ここ追加
end

# app/serializers/deck_serializer.rb 
class DeckSerializer < ActiveModel::Serializer
  attributes :id, :name
end

この状態でリクエストする。すると、次のように関連するモデルのデータもレスポンスにのってくる。

>> curl -s localhost:3000/users/1 | jq .
{
  "id": 1,
  "name": "user-0",
  "age": 1,
  "decks": [
    {
      "id": 1,
      "name": "deck-0"
    },
    {
      "id": 11,
      "name": "deck-10"
    }
  ]
}

同様に、 has_onebelongs_toというメソッドもある。ActiveRecordの規則に従っているようでわかりやすい。

アダプタを切り替える

標準で以下3種類のadapterが提供されていて、それらに切り替えることができる。

  • :attribuets (default)
  • :json
  • :json_api

rendering時のadapterパラメータに指定することで切り替え可能(※ グローバルな設定として持たせることも可能)。

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    render json: User.all
  end

  def show
    render json: User.find_by(id: params[:user_id]), adapter: :json # ★ ここ
  end
end

アダプタごとに出力形式に特徴がある。 例えばJSONアダプタ(:json)の場合はレスポンスのroot要素が作られるといった違いがある。

>> curl -s localhost:3000/users/1 | jq .
{
  "user": {
    "id": 1,
    "name": "user-0",
    "age": 1,
    "decks": [
      {
        "id": 1,
        "name": "deck-0"
      },
      {
        "id": 11,
        "name": "deck-10"
      }
    ]
  }
}

JSON APIアダプタ(:json_api)は、jsonapiというJSON形式でデータを返すAPIの仕様を決めている団体?のようなものがあり、そこで作られたversion1.0の仕様に従っているようだ。
各アダプタの詳細は Adapters に書かれている。

※ 余談:JSONアダプタという名称にはちょっと違和感を感じる。デフォルトのattributesアダプタもJSON返してるし...。

独自の属性を追加する

メソッドを定義するなどして、Modelの属性としては存在しない独自の属性を追加できる。 以下は、fooというキー名でbarという値を返すよう、メソッドの定義とattributesメソッドの引数変更をしたときの例。

# app/models/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
  attributes :id, :name, :age, :foo # ★ ココと..
  has_many :decks

  # ★ ココ
  def foo
    "bar"
  end
end

レスポンスは次のように変わる。

>> curl -s localhost:3000/users/1 | jq .
{
  "id": 1,
  "name": "user-0",
  "age": 1,
  "foo": "bar",  # ★ 増えた
  "decks": [
    {
      "id": 1,
      "name": "deck-0"
    },
    {
      "id": 11,
      "name": "deck-10"
    }
  ]
}

その他気になった機能

  • 関連先のモデルは includeオプションでも指定できる
    • render json: User.all, include: ['decks']
  • includeの値として * を渡すと、関連先のモデルを1階層分だけ出力する
    • render json: User.all, include: "*"
  • 同様に、** を渡すと、関連先のモデルを全て辿って出力する
    • render json: User.all, include: "**"
  • ApplicationControllerのように、各シリアライザ共通の処理をApplicationSerializerなどとして括り出せる(クラスの名称は何でも良さそう)
  • cache機能がある
  • key_transform optionsオプションで、出力するキー名のフォーマットを変更できる
    • camel, camel_lower, dash, unaltered, underscore, nil などが指定できる
    • 例:render json: User.all, key_transform: :camel

使ってみての雑感

直感的な設計になっていて、大体こんなかんじで動いてくれるだろうな〜っていうのがその通りに動いてくれる感じが良かった。
v1.0に向けて開発中のようだが、この辺の使い勝手の良さはそのまま維持されると嬉しい。

SOP(Same Origin Policy)とCORS(Cross Origin Resource Sharing)について調べた

SOP(Same Origin Policy)の理解が怪しいなぁと思ったのでちゃんとした仕様を調べた。
同時にCORS(Cross Origin Resource Sharing)についても調べることになったのでまとめて整理した。

SOP(Same Origin Policy: 同一生成元ポリシー)

  • Webブラウザなどのセキュリティ機能の1つ
  • ある オリジン から読み込まれた文書やスクリプトについて、そのリソースから他の オリジン のリソースにアクセスできないように制限するもの

※ 参考:https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy

オリジンとは

スキーム、ポート、ホストがそれぞれ等しいものが、同じオリジンと見なされる http://domain-a.com/dir/page.html をベースとした場合、次のようになる。

(o) http://domain-a.com/dir2/other.html
(o) http://domain-a.com/dir/inner/another.html
(x) https://domain-a.com/secure.html
-> スキームが異なる
(x) http://domain-a.com:81/dir/etc.html
-> ポートが異なる
(x) http://domain-b.com/dir/other.html
-> ホストが異なる

※ 補足

  • IPアドレスは見ていない
  • パスが異なるだけのものは問題ない

※ 参考:https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy#Definition_of_an_origin

制限されるもの

制限されないもの

SOP(同一生成元ポリシー)には許されているものがいくつかある。 以下、異なるオリジンに埋め込むことができるリソースの例。

  • <script src="..."></script> によるJavaScript
  • <link rel="stylesheet" href="..."> によるCSS
  • <img>による画像
  • <video>, <audio>によるメディアファイル
  • <object>, <embed>, <applet> によるプラグイン
  • @font-face で指定されたフォント
  • <frame>, <iframe>に関連する様々なもの
  • <form>からの送信

異なるオリジンへのアクセスを許可する方法

  • かつてはJSONPという手法が使われていた(JSONPについては省略)
  • 現在では CORS を使用することが推奨されているようだ

CORS (Cross Origin Resource Sharing)

  • SOPはセキュリティ上重要な仕組みだが、まっとうなWebアプリケーション開発者にとってはオリジンをまたいだアクセスが必要になるケースが多かった。そこでCORSが登場した
  • Webサーバがドメインをまたぐアクセスを制御する方法を規定することで、ドメイン間の安全な通信を保証する
  • つまり、CORSの仕様に則れば、ドメインをまたいだアクセス(クロスオリジンHTTPリクエスト)ができるようになる

※ 参考:https://developer.mozilla.org/ja/docs/Web/HTTP/HTTP_access_control#Overview

CORSのパターン

大きく2つのパターンがある

  • (1) simple cross-origin request
  • (2) actual request(プリフライトがある)

simple cross-origin requestになる条件は以下の通り

  • HTTPメソッドがGET,POST,HEADのいずれかである
  • HeaderがAccept,Accept-Language, Content-Language, Content-Type以外を含まない
  • Content-Typeを含む場合、その値がapplication/x-www-form-urlencoded, multipart/form-data, text-plainのいずれかである

上記の条件に合致しない場合はactual requestというプリフライトが必要なパターンになる

※ 参考:『Real World HTTP』10章

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術

プリフライトリクエストとは

  • 実際の通信の前に、権限確認目的で送信するリクエストのこと
  • プリフライトリクエストはHTTPメソッドのOPTIONSを使って送信される
  • リクエストには以下のヘッダを含む
    • Access-Control-Request-Method: 通信を許可してもらいたいメソッド
    • Access-Control-Request-Headers: 許可して欲しいヘッダー(カンマ区切り)
    • Origin: 通信元のウェブページのドメイン
  • プリフライトリクエストは、リクエスト前に毎回送信されるわけではない。通信内容を一定期間キャッシュすることができる。キャッシュ期間を制御するには以下のヘッダを使う

※ 参考:『Real World HTTP』10章

属人化の排除と責任感のトレードオフについて

スクラムでプロダクト開発をするようになって、約1年が経過した。

スクラムは実によくできたフレームワークであり、スクラムから得られたものはたくさんある。 例えば..

  • 今自分がやっていることに対して納得感を持って取り組むことができるようになった
  • 発生した問題に対して、その場しのぎではなく仕組みで解決することができるようになった
  • 同じゴールを共有するようになったことで、"チームでやっている"という感覚が強くなった。そのおかげで心理的負担が減り、同時にメンバー間も仲良くなった(と思う)
  • 優先順位に基いて業務を進めるようになったことで、属人化が減った

などである。

その一方で、デメリットとして最近感じているのは属人化を排除することで同時に個々人の責任感も削り落としてしてしまったのではないかという点である。

スクラム導入以前は、AさんはサービスAを担当し、BさんはサービスBを担当、というように個人が持つ領域が明確に分けて開発・運用をやっていた。今考えても、限られた人数でそこそこの規模のプロダクトを効率良く運営するにはこれ以外に良い方法が思い浮かばない。

当然この運営スタイルは属人化を強めてしまい、AさんはサービスAしか見れないし、また担当外のサービスBについてはほとんど関心が無いという状態になってしまった(少なくとも自分はそうなった)。 一方でメリットもあった。それは担当しているサービスに対する責任感が人一倍強くなるという点だ。障害が起きれば真っ先に対応するし、ユーザや関係者からの問い合わせがあればそれについても真っ先に対応する。サービスの短期的な面だけでなく、将来的な面についても検討することができていた。

スクラム開発では、スプリントバックログという形で1スプリントで取り組むタスクを優先順位で並べて可視化すると思う。優先順位でタスクを消化していくため、誰がどのタスクを取ることになるかはわからない。言い方を変えれば、どのタスクを取っても大丈夫という状態になっていなければならない(と理解している)。 このようなスタイルで開発を進めていくため、属人化された業務が減っていくわけだ。 そして、みんながサービスに対してある程度の知識を持つようになったことで、"ここは自分がやるんだ"という責任感も弱くなった。

心理学的に言えば 社会的怠惰 という現象に当てはまると思っている。「誰かがやってくれる」「自分が前に出なくても誰かが..」といった意識が無意識に働いているような気がする。 また「自分は1人ではなくチームでいる」という安心感が、危機感すらも鈍らせてしまったように思う。これまでは「何が何でも解決しなければならない」という気持ちだったものが、「今やれることをやっていこう。無理をしても仕方がない。」という方向になりやすくなった。

エンジニアの中で 属人化は悪 というのは常識のように語られ、自分もそれを信じていた。 しかし、もちろん自分の弱さもあるが、実際にそのデメリットを肌で感じることでこの通説に疑問を抱くようになった。今でも属人化が良いとは思っていないが。

何か、良い感じにバランスをとっていけるやり方は無いかなぁ。

...といったことを考えつつ、初心に帰ってスクラムの書籍でも読んでみようと思った2017年の秋だった。

SCRUM BOOT CAMP THE BOOK

SCRUM BOOT CAMP THE BOOK

とりあえずこれ買ってみた

三浦半島 自転車旅行記

三浦半島を自転車で巡ってきたのでその記録を残す。

三浦半島とは

三浦半島(みうらはんとう)は、神奈川県南東部にある半島である。

※ from: Wikipedia

地図で言うと大体この辺り。 f:id:mogulla3:20170910215547p:plain

ルート(当初計画)

ルートは、大まかに次のように設定した。

地元の駅 → 横須賀駅 → 道中色々 → 城ヶ島公園 → 逗子駅 → 地元の駅

地元の駅 ~ 横須賀駅と、逗子駅 ~ 地元の駅は輪行をすることにして、その他を自転車でめぐる計画だ。 ちなみに輪行とは、電車などの公共交通機関を使って自転車を運ぶことである。

イメージはこんな感じ。大体70 ~ 80kmくらい。 f:id:mogulla3:20170910220425p:plain

地元駅 ~ 横須賀駅(7:30 ~ 10:00)

輪行は今回が初の試みなので余裕を持って早めに出発したが、自転車を解体するのに 2時間30分 もかかるという大惨事。出だしから大きく躓く。

自転車を解体して輪行袋に詰め込んだやつ f:id:mogulla3:20170910214513j:plain

初の輪行で気づいたことメモ

  • スタンドが邪魔
  • ハンドルが横に長過ぎて袋にうまく収まらない
  • エンド金具はリアだけでとりあえず良さそう

横須賀駅到着(11:30 ~ 13:00)

横須賀駅に到着したあと、今度はバラバラにした自転車を戻す作業を始める。 解体は大変だったけど復旧は余裕だろと思っていたが、結局元に戻すのに 1時間30分 もかかるという惨事。

なお1時間くらいはブレーキのパッドを取り付けるのに苦戦していた。涙。 f:id:mogulla3:20170910221719p:plain

ヴェルニー公園

自転車の解体と復旧に合計4時間も取られて落ち込んでいたが、気を取り直してスタート。やっと旅が始まる。 横須賀駅の目の前にヴェルニー公園という見晴らしの良い公園があるのでそこにフラッと立ち寄ったが時間が無かったのですぐに去る。

とにかくお腹が空いたのでおいしいご飯を求めて自転車を漕ぐ。

海軍カレー @ どぶ板通り

横須賀といえば海軍カレーハンバーガだろうということで、そのあたりを求めさまよう。 横須賀駅から約1~2kmくらいのところに、どぶ板通りという場所があったのでそこで食べることにした。

どぶ板通り f:id:mogulla3:20170910222440j:plain

海軍カレー f:id:mogulla3:20170909135411j:plain

うみかぜ公園

海軍カレーでお腹を満たしたあとは、うみかぜ公園を目指すことにする。 といっても、どぶ板通りから2km程度なのですぐにたどり着く。

うみかぜ公園 f:id:mogulla3:20170909131255j:plain

猿島がすぐ近くに見える f:id:mogulla3:20170909142703j:plain

観音崎浦賀の渡し

うみかぜ公園からは海沿いをひたすら走っていく。 観音崎海岸、観音崎海水浴場、観音崎公園などを横目に、写真を取りつつ進んでいった。

うみかぜ公園から浦賀まで約11~13km走り、そこで小休止をはさみつつ、渡船で反対側まで自転車ごと運んでもらう。

浦賀の渡し f:id:mogulla3:20170910223730j:plain

自転車を渡船に乗せ優雅にショートカット f:id:mogulla3:20170909154100j:plain

三浦海岸、そして時間と体力との戦い

渡船を降り、再び海沿いを走り続けると、三浦海岸にたどり着いた。気がつけば三浦市に上陸していた。 三浦海岸以降も海沿いを走っていたが、ここで時間があまり無いことに気付く。ゴールである馬の背洞門には日が沈むであろう18:00までにたどり着きたい。が、このまま海岸線沿いを走っていてはどうにも間に合いそうにもない。

国道134号線・県道26号線に乗れると大幅にショートカットできるのでそこを目指そうとしたが、随分前にその道に入るところを通り過ぎていた。残された道は、海岸線沿いを走るか、山越えを覚悟してショートカットをするかだったが、体力と時間の関係上後者を選ぶしかなかった。

三浦霊園のすぐ隣あたりまで来ていたので、この霊園をぶち抜いて県道26号線を目指す。 三浦霊園は案の定、心を折ってくる急勾配な坂がたくさんあり、ここで初めて自転車を手で押すハメになる。厳しい。

城ヶ島上陸、馬の背洞門へ

山道を乗り越え県道26号に入ることに成功し、城ヶ島大橋を渡って城ヶ島に到着する。このとき17:30頃。 既に体力というか太ももが限界を迎えつつあり、階段を降りると足から崩れそうになる状態。

城ヶ島公園に自転車を止め、徒歩で馬の背洞門を目指す。

城ヶ島には猫が沢山いた f:id:mogulla3:20170909175051j:plain

馬の背洞門に辿り着く

城ヶ島公園から約400m歩き、馬の背洞門にたどり着く。この時17:40くらい。

やったー。馬の背洞門や! f:id:mogulla3:20170909173741j:plain

日没の様子。馬の背洞門は暗いと見えないのでマジでギリギリだったと言える。 f:id:mogulla3:20170909173720j:plain

鮮味楽で三崎のマグロを堪能する

目的地にたどり着いた我らは、最後においしい三崎のマグロを食べてこの街を去ることにした。 軽く調べ、鮮味楽(せんみらく)というお店に入る。

鮮味楽。立派な看板。 f:id:mogulla3:20170910230028j:plain

2000円超えのまぐろ丼。写真を取るのを忘れていて、わさび醤油が既にかかっている。 f:id:mogulla3:20170909184203j:plain

帰路につく

夕飯を食べ終わったときには19:00を過ぎていた。 当初は逗子駅まで行く予定だったが、時間・体力的に明らかに無理ということが判明する。そのため、進路を逗子から京急線三崎口駅に進路を変更し、そこから電車で帰ることにした。

城ヶ島 ~ 三崎口まで約8.5kmだったが、体力の限界を迎えつつあった我らを絶望させるような坂道がたくさんあった。何よりも太ももの筋肉が悲鳴を上げている。エアーサロンパスの購入を3回くらい検討したレベル。本当にきつかった。

なんとか三崎口駅までたどり着いた我々は、再び自転車の解体をするが、勝手がわかっていたので30分程度で終わる。 2時間30分 → 30分という急成長を見せる。 なお復帰時間はさらに短く 15分程度で完了した。

最後に

自転車旅行は行き先だけではなくて、そこに至るまでのルートを細かく調べる必要があり考えることは多くなる。しかし当初の計画では思い浮かばなかったようなスポットや料理に出会えたり、状況に応じて自由にルートを変更できるのが良い。 また、電車や車の旅行よりも当然疲れるが、自分の力で進んだという達成感もあり、思い出に残る旅になること間違い無し。

色んなトラブルもあったが、そこも含めて楽しめた日帰り旅行だった。