Madogiwa Blog

主に技術系の学習メモに使っていきます。

RubyonRails:脱.erb、Hamlのはじめの一歩

みなさん、Railsでアプリケーションを開発するときには、テンプレートエンジンは何を使っていますか? Railsではデフォルトだと.erbを使うことが多いと思いますが、今回はHamlを使ってみたので導入方法や使い方をメモしておきますφ(..)

Hamlのはじめの一歩

Hamlとは?

Hamlとは、Rubyの代表的なテンプレートエンジンです。 HamlHTML Abstraction Markup Languageの略で、 Google翻訳で約してみると、「HTML抽象化マークアップ言語」とのことです。

公式ページにもあるとおり、HTMLをより抽象的に綺麗に書くことが出来るテンプレートエンジンということですかねφ(..)

Haml is a markup language that’s used to cleanly and simply describe the HTML of any web document, without the use of inline code.
(Google翻訳)Hamlは、インラインコードを使用せずに、あらゆるWebドキュメントのHTMLをきれいに記述するためのマークアップ言語です。
File: REFERENCE — Haml Documentation より抜粋

実際にどれくらい注目されているか、erb等の他の主要テンプレートエンジンとGoogleトレンドで比較してみました。

デフォルトのerbと同じぐらい使われているみたいですね、デフォルトじゃないけどhamlに変える企業が多いということは、結構メリットも大きいのでは無いでしょうか?φ(..)

f:id:madogiwa0124:20180304232918p:plain

trends.google.co.jp

Hamlのメリット

私がhamlを書いて感じたメリットは、erbよりもシンプルに書ける点ですね。 例えば、同じユーザー作成フォームをhamlで書いたケースとerbで書いたケースを比較してみるとhamlの方がスッキリしてますね。
とっつきにくさはありますが慣れれば、hamlの方が書きやすいんじゃないかと思いましたφ(..)

erb

<h1>Sign up</h1>
<div>
  <%= form_for(@user) do |f| %>
    <%= render 'shared/error_messages',object: f.object %>
    <p>
      <%= f.label :name %>
      <%= f.text_field :name %>
    </p>
    <p>
      <%= f.label :email %>
      <%= f.email_field :email %>
    </p>
    <p>
      <%= f.label :password %>
      <%= f.password_field :password %>
    </p>
    <p>
      <%= f.label :password_confirmation,"Confirmation" %>
      <%= f.password_field :password_confirmation %>
    </p>
    <%= f.submit "Create my account" %>
  <% end %>
</div>

haml

%h1 Sign up
%div
  = form_for(@user, url: signup_path) do |f|
    = render 'shared/error_messages', { model: @user }
    %p
      = f.label :name
      = f.text_field :name
    %p
      = f.label :email
      = f.email_field  :email
    %p   
      = f.label :password
      = f.password_field  :password
    %p 
      = f.label :password_confirmation, "Confirmation"
      = f.password_field  :password_confirmation
    %p
      = f.submit "Create Account"

Hamlの導入方法

導入方法は、とても簡単で下記をGemfileに記述し、bundle installを行うだけです。
これだけで、rails g実行時に自動生成されるテンプレートがaction.html.hamlのようなhaml形式のファイルになります!

gem 'haml-rails'

基本的な使い方

hamlの基本的な使い方を記載しておきます。
すごくざっくり説明すると、変数を表示する場合は= @hoge、要素を挿入する場合は%divというように使用しますφ(..)

基本的な部分のerbとの対応表を記載したので、参考にしてみてください。

分類 erb haml
HTML要素 <p></p> %p
変数の表示 <%= @hoge %> = @hoge
処理の実行 <% hoge = "hoge" %> - hoge = hoge
idの指定 <p id="hoge"></p> %p#hoge
classの指定 <p class="hoge"></p> %p.hoge

基本的な記法については、下記のページに丁寧に記載頂いてます!m( )m

morizyun.github.io

Tips集

私が実際に使って、少し悩んだ部分をTipsとしてメモしておきますφ(..)
なんかあればまた追記します!

文字列を1行で要素に設定したい

Haml

%h1 Sign up

実際にレンダリングされるHTML

<h1>Sign up</h2>

通常の文字列と変数を1行で設定したい

Haml

%div= "#{current_user.name}さん、ようこそ"

実際にレンダリングされるHTML

<div>ログインユーザーさん、ようこそ</div>

特定の条件の場合だけ出力したい

Haml

%div= "#{current_user.name}さん、ようこそ" if current_user

Ruby:Rubyの25周年イベント「Ruby25」に参加してきた

Ruby25周年イベント

本日は、Rubyの25周年のイベントRuby25に参加してきました! Rubyは日本で生まれ、世界中で使われているプログラミング言語です。

そんな日本産のプログラミングが四半世紀普段自分が勉強で使っている言語が25周年という節目を迎えるのは、すごく喜ばしいことだと思いました!:D

そんな記念すべきイベントRuby25の内容を参加した記念として、超超粗々ですがメモを残しておくφ(..)※参考リンクや記載内容をあとで綺麗にしたい。。。

※その場で取ったメモなので記載不足、誤記等があると思います、すいません。。。

特別講演「Rubyの1/4世紀」

Rubyのいままでに関するお話しφ(..)

Rubyの歴史

↓詳しい内容は下記にてまとめて頂いております。

github.com

1995年

  • Rubyが正式リリース

1997年

  • ネット記事や書籍でルビーが紹介
  • OSP'97入選

参考リンク * https://www.iajapan.org/osp/osp97.html

1999年

参考リンク * https://www.ruby-lang.org

2001年

  • RubyWorldConference開催

参考リンク * http://2017.rubyworld-conf.org/

RailsRubyに与えた影響「Rubyのパッケージ管理」

  • パッケージ管理をRubyGems
  • Bundlerのリリース

おわりに

専門家は私達しかいないんだ まつものゆきひろさんの書籍から引用

プログラミング言語を広めることは難しい。 多くの個人が手探りで進めてきた。

組織としては、個人の活動やインフラを支援してきた。 * フルタイムコミッター * イベントスポンサー

基調講演「Ruby after 25 years」

Rubyのこれからに関するお話しを、まつもとゆきひろさん自身から聞くことが出来ましたφ(..)

Rubyはいつ生まれたか?

  • Rubyという名前が付けられた日が生まれた日
    • 1993/2/24
  • なぜRuby
    • Perlを元にしたから宝石の名前、CoralかRubyが候補にあがり、高級感のあるRuby

参考リンク https://freelance.levtech.jp/guide/detail/113/まつもとゆきひろさんは、同僚の誕生石というのは否定していたように思いますが笑

Rubyの未来予測

  • 多様性が減少傾向にあるところ
  • 逆に変化したところ
    • ハードウェア関連(スペック等)
    • アーキテクチャ(Web,モバイル)
    • キーワード(AI,IOT)
    • データ、コード、チームの規模
  • 未来のRuby

参考リンク https://ja.wikipedia.org/wiki/チューリング完全

Ruby3

  • 高速
    • MJIT※Ruby2.6から
    • Ruby3x3※Rubyを3倍速くしよう!プロジェクト
  • 分散
  • 解析
    • 静的解析
  • Ruby3はラベル、大きなギャップはつくらない
    • 過去の1.8と1.9の分断を考慮

さらなる未来

  • 開発効率と保守性の向上
    • IDE、静的解析、rubocop、success based、プロファイラ型解析、テディベアプログラミング
  • 分散性※あやふや
    • マルチコア、マルチDC、コンカレンシー(分散処理)、Faas(AWS Lamda)
  • 非均質計算環境対応
    • 複数のPCかつ性能が違うものを役割ごと分担して扱う

生物学的には25年は変わらない、でも文化は変わる。

「コンピュータは楽しい」

Rubyの価値の本質 = 人間のためのRuby

私達のRuby、私達が未来をつくる

おまけ

まつもとゆきひろさんのプライベート情報が、ご家族からありました笑 * 探しものが嫌い * 親ばからしい * 人から可愛がられるのがうまい * インドア派、いつもパソコンしてる * 好きなもので生きていくを体現している

参考リンク https://ja.wikipedia.org/wiki/%E4%B8%A6%E8%A1%8C%E6%80%A7

Ruby on Ruby on Rails

RubyとWeb≒Ruby on Railsについてお話しφ(..) とても楽しそうに離されているのが印象的でした!!!

Ruby on Railsとは

RailsRubyをメジャー言語に押し上げた原動力、RubyRailsは切っても切れない関係 * 特徴 * 現場のリアルな問題を解決する * DHHが作ったBaseCampのプロダクトをOSS化(実践的) * 常に変化し続ける * 5000人のコントリビューター * 常に最新のWebフレームワークであり続けている。 * ずっと止まらずに走り続けている = Railsという名前に込められた思い * REST,Bundler等あたらしい価値観の提供 * 楽しい * 10年以上やってるけど楽しい * 気持ちよくWebアプリが作れる * 儲かる * 数多くのプロダクトを作ってきた実績 * Twittercookpad、MoneyForward * スピード感がある * rubyを決めてお金をもらえる時代が来た! is 最高

Rails on Rubyでは?→Rubyの良さを世界に伝えるインフラ

Railsはこの先どうなるのか?

  • 先週ぐらいからRails6の開発に着手
  • 常に最新を意識、Webが廃れるまでは絶対勝者なのでは?
  • あと25年ぐらいはいけるのでは?

RubyとInfrastructure as Code

Rubyとインフラについてのお話しφ(..)

Infrastructure as Code

サーバインフラの構成管理、初期化、クラスタリング等をコードで表現すること。

Rubyはどのように強みを出していく??

Rubyの強みとインフラ

  • DSLに強みがあり、柔軟にインフラ要件を記述出来る
  • RubyDSLを採用したプロダクト※configrationに強み
# こんな感じ・・・?
user do 
  id: hoge
  pass: hoge
end

インフラエンジニアの生産性を上げる → 「Enjoy Programing for Ops

最近のインフラ関連事情

Webサービス運用

  • マイクロサービス
    • 大きなサービスを機能単位に分ける
  • コンテナ化
    • VMよりも柔軟かつ精緻に制御出来るコンテナ技術
  • サーバーレス

mrubyのミドルウェア組み込み

  • mrubyでミドルウェアを書くメリット
    • 素早い検知からのフック処理
    • Configration as code

新しい形のオーケストレーションRubyとmrubyで実現する未来を作りましょう!

mrubyって今こんな風に使われています

軽量ruby=mrubyの現状についてのお話しφ(..)

mrubyの開発背景 RubyでIOTのような組込開発出来ると良いのでは?」

mrubyの特徴

  • 軽い
    • mruby : RAM 200Kで動く
  • 使いやすい
    • C:35行→mruby:9行
    • VM上で動作、大抵のOSに対応、mrbGemsによるライブラリ管理

だけど組込業界には、なかなか使って貰えなかった。。。 →IOTの注目により徐々に広まってきた!!

実際のプロダクト * IIJ:インターネットルーター(SA-W2) * データテクノロジー:IOTゲートウェイ * エフェクト:アルコール検知器 * ゲームやWebサービスでも使われている * ニーアオートマター * mruby-cli

mrubyの情報

  • mrubyのフォーラム

mrubyのこれから

  • ベースをruby2.0系に更新
  • カンファレンスの企画

Data Processing and Ruby in the World

Rubyとデータ解析(Data Proccessing)についてのお話しφ(..)

Data Proccessing?

  1. COLLECT→集める(fluentd,Logstash)
  2. SUMMARIZE→集計する(RedDataTools)
  3. ANALYZE→解析する(Ruby/Numo,SciRuby)
  4. VISUALIZE→可視化する

※Visualizeには、OSSが未成熟でありWebサービス(Rubyを使っているもの=Repro,TREASURE DATA,FlyData)に支配されている→チャンス!

COLLECT

  • fluendd:RubyMicrosoftのサービス、ログを集めるエージェント
  • logstash:JRuby

キーワード:kubernetes

Rubyは、データ収集に強みあり。

データ解析関連の注目プロジェクト

Rubyの未来を語る

Rubyの未来を語る対談のメモですφ(..)

rubyの開発体制について

  • プロダクトオーナーは、一人の優しい独裁者によって行われるのほうが良いと考えている。事故等への対策は、今から発言を蓄積し、AIがデザインするプログラミング言語も目指すのも面白い笑

    ruby3について

  • 別のブランチを立てるのではなく、メインラインで開発を行っていき、一定のタイミングでruby3とする方針を考えている
  • バージョンアップの複雑化によるコミュニティの分断を考慮している。原則として、非互換のものは入れない予定。
  • どうしても変えたい部分が無いと言えば嘘となるが、諦めなければ行けない部分もあると考えている。
  • MJITは高速化に寄与するが、立ち上げのパフォーマンスに懸念があるため、デフォルトでONとするかどうかは、継続検討していく方針。
  • FroozeStringLiteralのデフォルト適用は議論されている。

    Rubyの型について

  • 型はやはり書きたくない。型が無くても動くものに型を書くのはDRYじゃない気がしている。
  • 型推論、動的型付け、typescriptのように型情報を別ファイルに記載する等のIDEのコーディング支援を強化する等は検討していく。

    Ruby3以降の改修について

  • 現状のrequireでは、requireするファイルとローカルファイルで名前が競合すると辛みがあるので、名前空間については、PythonJava等を参考に再検討することも考えている。

    人間を助けるための技術について

  • Rubyの思想としては、人側の価値を寄せるべきと考えている。できるだけ、人側が何をしたいかを記述して動くようなプログラミング言語としていきたい。
  • 人工知能にコードを大量に与えて、アウトプットをコンピュータに任せることも出来ると思うが、写真が出来ても絵がなくならないように、人間がプログラムを書く文化も残っていくと感じている。

    RubyRailsについて

  • Webについては、まだPHPのほうが多いイメージだが、複雑性の高いものやシンプルなものに関してもRailsSinatra等があり競争力は高いと考えている。
  • RailsについてはRubyの保守的な判断と対象的に、挑戦的なアップデートを行っている。それは、それぞれの役割的な判断で行われていると思う。
  • そのような各人が自由な判断でRubyをカスタマイズしていっているのは、良いことだと感じている。そのような実例からRubyの機能に取り込んでいくこともしていける。
  • 自由にやれる反面、やりすぎるとRuby本体には入れられない等困ることもある笑、しかし禁止することは考えていない。
  • そのためRailsRspec等の周辺モジュールを覚える学習コストは仕方ない部分はある。コミュニケーションを取りながら学べる環境が良い。テディベアプログラミング笑

    教育現場について

  • 最近は、Pythonが癖のなさやAI等の隆盛から流行しているが、それはPythonのエンジニアチームがAI等に努力し続けていた結果であり、賞賛すべきと感じている。
  • PythonRubyは言語仕様が似ている部分があるので、言語の移行については、そこまで難しくないというイメージ。そもそもゲーム・チェンジの可能性は、いつでも起こりうるのでロックインするようみオープンなマインドでいた方が良いんじゃないかという感じ。

    今後25年について

  • 目標はとりあえず、SURVIVE。未来予想が難しい中、生き残っていくのはハードなチャレンジだと感じている。どうやって環境変化をキャッチアップしていくかは、重要だと感じている。あとは健康問題笑
  • Rubyはライフワーク、ずっとやっていきたい。サイドプロジェクトとして、色々なことにはチャレンジしていきたい。個人的には、DBといったミドルウェア等のアプリケーションの下位レイヤーに興味がある。最近だDeniss(?)といったDBシステムが気になっている。

おわりに

Ruby25周年という節目に立ち会えてすごく良かった!と思います。 登壇者及び参加者の皆様が、すごく楽しそうに参加していたのがすごく印象的でした!!! これもRubyのコンセプトである「楽しさ」が、プログラミング言語だけでなくコミュニティにも伝わっているのかなと思いました。

自分自身としても楽しさを忘れずにエンジニアとして活躍していきたいと思いました!!

Ruby:継承を使って良い感じにコードを共通するメモ

オブジェクト志向設計実践ガイドを読んで継承を使ったソースコードの共有化手法を学んだので内容を整理してみましたφ(..)

はじめに

今回は、下記のような社員を表すクラスのコードを元に実際に継承によるコード共有を行ってみようと思いますφ(..)

class Staff
  attr_reader :name, :group
  
  def initialize(args)
    @name  = args[:name]
    @group = args[:group]
  end
  
  def work
    do_preparation
    do_clean_up
  end
# ...
end

社員だけでなく管理者も追加して、独自のプロパティとメソッドを定義したいんだけど。。。

あまり考えたくないですが、このような仕様変更は結構ありそうですね・・・!! このような要求に関してどのようにコードを修正しますか??
ぱっと思いつくのはgradeを追加して、その値によって振る舞いを定義する方法でしょうか?

class Staff
  attr_reader :name, :group, :grade, :assistant
  
  def initialize(args)
    @name  = args[:name]
    @group = args[:group]
    @grade = args[:grade]
    # 管理者の場合、assistantを持つ
    @assistant = args[:assistant]
  end
  
  def work
    def do_preparation
    if grade == :admin
      # 管理者の場合はチェックを行う
      do_check
    else 
      do_clean_up
    end
  end
# ...
end

一見良さそうに見えますが、このコードは下記のような問題を含んでいます。

  • 新しいgradeが追加された場合、workにif文が追加されメンテナンス負荷が高い。
  • 新しいgradeが追加される度に、プロパティが追加され、使用するプロパティと使用しないプロパティが混在する恐れがある。

クラスは原則として単一の役割を持つ(単一責任)であることが良いと言われています。しかし今回の改修では、Staffクラスが純粋なスタッフとしての定義と管理者としての定義という2つの役割を持ってしまっているのが原因のようですね。。。

継承とはなんぞや

継承を使ってコードを整理する前に、そもそも継承とは何なのかを少しおさらいしましょう。

継承とは、根本的に「メッセージの自動移譲」の仕組みにほかなりません。
オブジェクト指向実践ガイドより抜粋

継承とはスーパークラス(継承元クラス)からサブクラス(継承先クラス)へ、プロパティやメソッドを共有化する仕組みです。

サブクラスでは、スーパークラスのメソッドとプロパティが使えるだけでなく、スーパークラスへ定義したメソッドの上書き(オーバーライド)及び、独自のメソッドも定義することも出来ます!!

イメージとしては師匠(スーパークラス)から弟子(サブクラス)へ、礼儀や技を受け継ぎ、弟子は師匠から受け継いだ技だけでなく、独自に編み出した技も使えるイメージでしょうか(._.)

継承を使ってコードを整理してみる

では本題の継承を使ってコードを整理してみますφ(..)
現状のコードは、Staffクラスの中に一般社員と管理者、2つの役割が存在してしまっているために煩雑になってしまっていましたね。

より抽象的なクラスを作成し、継承させてみる。

まずは、一般社員や管理者よりも抽象的な労働者Employeeクラスを作成してみましょう。その後、管理者Managerのクラスを作成し、労働者クラスを管理者や一般社員に継承させてみましょう。

class Employee
end

class Staff < Employee
  attr_reader :name, :group, :grade, :assistant
  
  def initialize(args)
    @name  = args[:name]
    @group = args[:group]
    @grade = args[:grade]
    # 管理者の場合、assistantを持つ
    @assistant = args[:assistant]
  end
  
  def work
    do_preparation
    if grade == :admin
      # 管理者の場合はチェックを行う
      do_check
    else 
      do_clean_up
    end
  end
# ...
end

class Manager < Employee
end

一見この状態では、なにも変わっていないように見えますが、ここからStaffクラスの抽象化可能な処理をEmployeeに移譲していきます。
※処理を全てEmployeeに移譲してから、StaffManagerに振り分けるよりも、Staffから抽象化出来そうな処理を吟味して、Employeeに移譲していった方が、抽象的で無い処理がEmployeeに残るリスクを回避することが出来ます。

まずは、プロパティから労働者Employeeに移譲してみましょう。 @name@group@gradeは、労働者であれば誰でも持っていると言えそうなので、Employeeに移譲しても良さそうです。また、@assistantは管理者のみが持つ項目なので、Managerに移します。

class Employee
  attr_reader :name, :group, :grade
  def initialize(args)
    @name  = args[:name]
    @group = args[:group]
    @grade = args[:grade]
  end
end

class Staff < Employee
  def work
    do_preparation
    if grade == :admin
      # 管理者の場合はチェックを行う
      do_check
    else 
      do_clean_up
    end
  end
# ...
end

class Manager < Employee
  attr_reader :assistant
  initialize(args)
    # superを書くことで、サブクラス(Employee)の同名のメソッドを呼び出せる
    super 
    @assistant = args[:assistant]
  end
end

次にStaffworkメソッドに取り組んでみます。メソッド内の作業前の準備do_preparationは、Employeeに移して、他は個別にサブクラスに実装した方が良さそうですね。

class Employee
  attr_reader :name, :group, :grade
  def initialize(args)
    @name  = args[:name]
    @group = args[:group]
    @grade = args[:grade]
  end

  def work
    do_preparation
  end
  # ...
end

class Staff < Employee
  def work
    super
    do_clean_up
  end
# ...
end

class Manager < Employee
  attr_reader :assistant
  def initialize(args)
    super # Employeeのinitializeメソッドを実行
    @assistant = args[:assistant]
  end
  
  def work
    super
    do_check
  end
  # ...
end

最初に比べるとStaffが純粋な社員としての役割となっていて大分良い感じになってきた気がします!!

しかし、この段階でも問題を含んでいます。。。

  • StaffなのにgradeadminManagerなのにgradestaff等のチグハグな状態が起こる。
  • 新しいサブクラスを追加する際にsuperを記載忘れると上手く動作しなくなってしまう。

このような問題を解決するために「テンプレートメソッド」、「フックメソッド」を使って更にリファクタリングしていきます。

テンプレートメソッドをつかってサブクラスにメソッド実装を促す。

「StaffなのにgradeadminManagerなのにgradestaff等のチグハグな状態が起こる。」をテンプレートメソッドというデザインパターンで対応してみたいと思います!!

テンプレートメソッドは、スーパークラスにメソッドを作っておいて、サブクラスでのオーバーライドを促すというものです。

実際にテンプレートメソッドを用いてリファクタリングしたのが下記コードです。Employeedefault_gradeメソッドを実装し、その中にERRORを発生処理を実装し、initializeメソッドで@gradeの設定に使用することでサブクラスにdefault_gradeの実装を促しています。

これによって、StaffManagerにて適切なgradeが設定されるようになりましたφ(..)

class Employee
  attr_reader :name, :group, :grade
  def initialize(args)
    @name  = args[:name]
    @group = args[:group]
    @grade = default_grade
  end
  
  def default_grade
    # ERRORを投げるメソッドを定義
    raise NotImprementedError, 
    "#{self.class}では、default_gradeメソッドを実装してください。"
  end

  def work
    do_preparation
  end
  # ...
end

class Staff < Employee
  def work
    super
    do_clean_up
  end
  
  def default_grade
    :staff
  end
# ...
end

class Manager < Employee
  attr_reader :assistant
  def initialize(args)
    super # Employeeのinitializeメソッドを実行
    @assistant = args[:assistant]
  end

  def default_grade
    :admin
  end
  
  def work
    super
    do_check
  end
  # ...
end

フックメソッドを使ってスーパークラスからsuperを除外する。

「新しいサブクラスを追加する際にsuperを記載忘れると上手く動作しなくなってしまう。」をフックメソッドを使って対応していきます。

フックメソッドを使うとサブクラスでsuperを実装させずに、サブクラスの処理を引き継ぐことが出来ます!!

実際のコードを見たほうがわかりやすいと思います、フックメソッドを用いてリファクタリングしたのが下記コードです。

Employeepost_initializesubsequent_workを実装し、initializework内で呼び出すことで、StaffManager内でpost_initializesubsequent_workメソッドを実装すれば、superを使わなくも自動的に元メソッド内で実行されるようになりました!!

class Employee
  attr_reader :name, :group, :grade
  def initialize(args)
    @name  = args[:name]
    @group = args[:group]
    @grade = default_grade
    post_initialize(args) # フックメソッド
  end
  
  def post_initialize(args)
    nil
  end
  
  def default_grade
    # ERRORを投げるメソッドを定義
    raise NotImprementedError, 
    "#{self.class}では、default_gradeメソッドを実装してください。"
  end

  def work
    do_preparation
    subsequent_work # フックメソッド
  end
  
  # ...
end

class Staff < Employee  
  def subsequent_work
    do_clean_up
  end
  
  def default_grade
    :staff
  end
# ...
end

class Manager < Employee
  attr_reader :assistant
  def post_initialize(args)
    @assistant = args[:assistant]
  end

  def default_grade
    :admin
  end
  
  def subsequent_work
    do_check
  end
  # ...
end

継承を使ったコードをテストする

最後に継承を使ったコードのテストについて整理していこうとおもいますφ(..)

検証のポイント

継承を使ったコードの検証のポイントは下記の通りです。

スーパークラス

  • メソッドが定義されているか
  • テンプレートメソッドが適切にエラーを吐くか
    • サブクラスでdefault_grade未実装時にエラーとなるか

サブクラス

  • スーパークラスのメソッドが定義されているか
  • サブクラス独自の振る舞いが定義されているか
    • post_initializesubsequent_workdefault_gradeがサブクラスに実装されているか
    • サブクラスで定義したdefault_gradeの設定値の確認

テストコード

それでは実際にテストコードを書いて検証していきます!(._.)

メソッドが定義されているか、スーパークラスのメソッドが定義されているか

スーパークラスにメソッドが定義されており、それが適切にサブクラスに継承されているかを検証するにはassert_respond_toを使います。また、メソッド定義の確認は汎用性を高めるために、moduleを使ってまとめておきます。
moduleにまとめておくことにより、新規のサブクラスが追加になっても容易にスーパークラスのメソッド追加出来ますφ(..)

require 'minitest/autorun'

# スーパークラスのメソッド定義を検証するモジュール
module EmployeeInterfaceTest
  def test_respond_to_name
    assert_respond_to(@object, :name)
  end
  def test_respond_to_group
    assert_respond_to(@object, :group)
  end
  def test_respond_to_grade
    assert_respond_to(@object, :grade)
  end
  def test_respond_to_work
    assert_respond_to(@object, :work)
  end
  def test_respond_to_default_grade
    assert_respond_to(@object, :default_grade)
  end
  def test_respond_to_do_preparation
    assert_respond_to(@object, :do_preparation)
  end
end

# スーパークラスの検証
class EmployeeTest
  # モジュールをincludeし、メソッド定義の検証を実施
  include EmployeeInterfaceTest
  def setup
    @employee = @object = Employee.new(geade: :worker)
  end
end

# サブクラス(Staff)の検証
class Staff
  # モジュールをincludeし、メソッド定義の検証を実施
  include EmployeeInterfaceTest
  def setup
    @staff = @object = Staff.new
  end
end

# サブクラス(Manager)の検証
class Manager
  # モジュールをincludeし、メソッド定義の検証を実施
  include EmployeeInterfaceTest
  def setup
    @manager = @object = Manager.new
  end
end

テンプレートメソッドが適切にエラーを吐くか

スーパークラスに定義されたテンプレートメソッドが適切にエラーを出力するかを検証するのは、単純です。assert_raiseを使用して、NotImprementedErrorが発生するかどうかを検証します。

また、エラーが発生することだけではなくオーバーライドされていたらエラーとならず、サブクラスで定義した値が設定されることをスタブを使い確認しています。今回は、サブクラスとしてStaffManagerが定義されていますが、実際の業務等ではスーパークラスだけで検証する必要がある場合は、このようにスタブを作成することでスーパークラスの検証を行うことが出来ますφ(..)

# 検証用のサブクラス(スタブ)
class StubbedEmployee < Employee
  def default_grade
    :stubbed
  end
end

# スーパークラスの検証
class EmployeeTest
  # モジュールをincludeし、メソッド定義の検証を実施
  include EmployeeInterfaceTest
  def setup
    @employee = @object = Employee.new(geade: :worker)
    # スタブ用のインスタンスを作成
    @stubbed_employee = StubbedEmployee.new
  end
  
  # スーパークラスで呼び出したときにエラーとなることを検証
  def test_forces_subclasses_to_implement_default_grade
    assert_raises(NotImprementedError){ @employee.default_grade }
  end
  
  # スタブを用いて、オーバーライド時に値が設定されることを検証
  def test_default_grade_overraid_success
    assert_equal @stubbed_employee.default_grade, :stubbed
  end
end

サブクラス独自の振る舞いが定義されているか

これも最初に説明したケースと同様にサブクラス固有音メソッド定義を検証するmoduleを作成して検証していきます。また、固有の固有の振る舞いdefalt_gradeの設定値については、テストメソッドとして、個別のテストクラスに記載していきますφ(..)

こうすることによって、新しいサブクラスが発生したときにEmployeeSubclassTestをincludeすれば、サブクラスに必要な役割の検証を行うことができ、あとは個別の振る舞いのみの検証に集中することが出来ます!

module EmployeeSubclassTest
  def test_respond_to_post_initialize
    assert_respond_to(@object, :post_initialize)
  end
  def test_respond_to_subsequent_work
    assert_respond_to(@object, :subsequent_work)
  end
  def test_respond_to_default_grade
    assert_respond_to(@object, :default_grade)
  end
end

# サブクラス(Staff)の検証
class Staff
  # モジュールをincludeし、メソッド定義の検証を実施
  include EmployeeInterfaceTest
  # moduleをinclude
  include EmployeeSubclassTest
  def setup
    @staff = @object = Staff.new
  end
  # default_gradeの値の検証
  def test_default_grade_value_staff
    assert_equal @manager.default_grade, :staff
  end
end

# サブクラス(Manager)の検証
class Manager
  include EmployeeInterfaceTest
  # モジュールをincludeし、サブクラスのメソッド定義の検証を実施
  include EmployeeSubclassTest
  def setup
    @manager = @object = Manager.new
  end
  # default_gradeの値の検証
  def test_default_grade_value_admin
    assert_equal @manager.default_grade, :admin
  end
end

おわりに

今回は少し長くなってしまいましたが、継承によるコードの共有化と、継承を使ったテンプレートメソッドフックメソッドを使ったリファクタリングについて整理してみましたφ(..)

継承を使うとコードを共有してコード量を削減出来るだけでなく、 コードの変更にも強くなるので積極的に使っていきたいですが、安易に継承を使い過ぎると具象的な内容を残してしまったり、処理が各所に散らばり可読性が下がってしまう恐れもあるので、注意して使っていきたいですね・・・!

Ruby:ダックタイピングを使って抽象度の高い処理を実装するメモ

オブジェクト志向設計実践ガイドを読んでダックタイピングを使って抽象度を上げる方法を学んだので、整理してみるφ(..)

はじめに

突然ですが以下のようなコードを見て、どのように思いますか?

class EmploymentHandler
  def work(employees)
    employees.each do |employee|
      case employee
      when Staff   then employee.do_clean_up
      when Manager then employee.do_check
      end
    end
  end
end

class Staff
  def do_clean_up
    # ...
  end
end

class Manager
  def do_check
    # ...
  end
end

渡された引数によってcase文使い、引数に渡されたクラスによって実行する処理を分けています。

一見良さそうに見えますが、このコードには以下のような問題が含まれています・・・。

  • EmploymentHandler.workメソッドに渡されるクラスが増える度にwhen...thenを追加しなければならない。
  • EmploymentHandler.workメソッドを修正する場合に、呼び出すStaff,Managerのメソッドについても理解しなければならない。

今回のようなケースでは、ダックタイピングを用いることで、EmploymentHandler.workの抽象度を上げ、他Classの修正から守り、よりわかりやすいコードとすることが出来ます!

ダックタイピングとは

ダックタイピングは、下記のような考え方です(._.)

「もしオブジェクトがダック(アヒル)のように鳴き、アヒルのように歩くならば、そのクラスが何であれ、それはダックである」
オブジェクト指向実践ガイドより抜粋

つまり、渡された引数がなんであれ、同様のメソッドを持っていれば同じものとして扱い、中身の処理は関知しないということでしょうか。

ダックタイピングを使ってみる

では実際にダックタイピングを使ってコードをリファクタリングしてみようと思います!

今回のケースであれば、Staff.do_clean_upManager.do_checkというメソッドをEmploymentHandler.workの中で呼び出していますが、社員または管理者がどのような働きをするかを定義しています。

なので、まずは既存のメソッドをラップするようなStaff.workManager.workというメソッドをつくってみましょう。

class EmploymentHandler
  def work(employees)
    employees.each do |employee|
      case employee
      when Staff   then employee.work
      when Manager then employee.work
      end
    end
  end
end

class Staff
  def work
    do_clean_up
  end
  def do_clean_up
    # ...
  end
end

class Manager
  def work
    do_check
  end
  def do_check
    # ...
  end
end

どうでしょうか、case文内でClassによって処理は分岐していますが実行されるメソッドはemployee.workとなっています!

引数と実行されるメソッドが同じになったので、ダックタイピングが使えそうですね!!

さっそくダックタイピングを使ってリファクタリングしたコードが下記ですφ(..)

class EmploymentHandler
  def work(employees)
    employees.each do |employee|
      employee.work
    end
  end
end

class Staff
  def work
    do_clean_up
  end
  def do_clean_up
    # ...
  end
end

class Manager
  def work
    do_check
  end
  def do_check
    # ...
  end
end

リファクタリング前後でコードを比較してみると、リファクタリング後の方が抽象度が上がり変更に強くなっていますね!!

修正前 修正後
EmploymentHandler.workメソッドに渡されるクラスが増える度にwhen...thenを追加しなければならない。 workメソッドを持つクラスを渡すというルールを守れば、どのクラスでもEmploymentHandler.workを使うことが出来る。
EmploymentHandler.workメソッドを修正する場合に、呼び出すStaff,Managerのメソッドについても理解しなければならない。 EmploymentHandler.workは、workメソッドが実装されていることを信頼し、呼び出し元のクラスのことは知る必要がない。

ダックタイピングをテストする

最後にダックタイピングのテストについて考えてみようと思います。 ダックタイピングのテストに必要なことは、それぞれのクラスについて特定の同名メソッドが実装されているかどうかを検証する必要があります。
今回は、Staff及びManagerworkメソッドが実装されているかを検証します。※テストはminitestを使用しています。

require 'minitest/autorun'

# workが実装されているか検証するモジュール
module WorkInterfaceTest
  def test_implements_the_work_inmterface
    assert_respond_to(@object, :work)
  end
end

# 社員用のテスト
class StaffTest < MiniTest::Unit::TestCase
  include WorkInterfaceTest
  def setup
    @staff = @object = Staff.new
  end
end

# 管理者用のテスト
class ManagerTest < MiniTest::Unit::TestCase
  include WorkInterfaceTest
  def setup
    @manager = @object = Manager.new
  end
end

WorkInterfaceTestモジュールをStaffTest及びManagerTestにincludeすることにより、それぞれのクラスにworkが実装されていることを検証しています。

またworkの実装有無をWorkInterfaceModuleTestで検証することにより、新しいクラスが追加されても容易にテストを追加出来るようになっていますφ(..)

まとめ

私もまだまだ理解が不足している点もあるのですが、簡単にダックタイピングについてまとめて見ました!

今回のケースではcase...whenを使っている箇所をダックタイピングでリファクタリングしてみましたが、responds_to?kind_of?等を使っている場合も使えるそうですφ(..)

ダックタイピングを上手く活用出来ると綺麗で、変更に強いコードを書けそうなので、使えそうな部分では積極的に活用して身に付けていきたいと思いました。

Dockerのトレンド状況と学ぶメリットとハードルについて

先日、下記の勉強会に参加してDockerの基礎的な部分は知ることでき、せっかくなのでちゃんとDockerを勉強しようと思い、ただけんさん(ただけん@electron職人になりたい (@tadaken3) | Twitter)に紹介頂いた下記書籍を購入して勉強しましたφ(..)

Dockerの基礎や使い方については、色々な記事でまとめられているので、ここではDockerの状況、Dockerを学ぶメリット及びつまづきそうな部分を整理してみようかと思います(._.)!

employment.en-japan.com

目次

Dockerってそんなに使われているの?

Dockerってよく聞くけど、本当に現場で使われているのか気になったので、色々と調べてみましたφ(..)

まずは、GoogleトレンドでDockerと良く比較されるその他の仮想環境ツールを比較してみました。 下記グラフの青がDockerです。Googleトレンドを見てみると最近では、もう他の仮想環境ツールよりも注目されはじめているようですね!

f:id:madogiwa0124:20180128220514p:plain ※赤:VirtualBox、黄色:Vmwareです。

trends.google.co.jp

Qiitaの投稿数もDockerが圧倒的ですね!!※2018/01/28現在

キーワード 投稿数
Docker 10237
Vmware 1828
Vagrant 6723
VirtualBox 5108

Dockerは、もう企業でも大分使われてますね!!

tracpath.com

結果だけ見ると、大分エンジニアとして働く上での必須スキルになりつつあるのかなぁ、と感じました(゜_゜)

Dockerを学ぶとなにが良いか?

Dockerを少し学んでみましたが、具体的なメリットとしては下記のような感じかなと思いました!

  • 他人が作った環境を再現出来るので、環境構築で躓く可能性が下がる。
  • 何か起きてもすぐに環境の破棄・再構築が出来るので、間違えても安心。

環境構築で躓くと辛いですし、なにかホスト側で環境構築してコマンドを間違えてしまったりするとリカバリが大変です。。。
Dockerを使えば他の人が作ったDockerImageを元に環境を再現でき、破棄と再構築もコマンド一つで出来るので、大分楽だと思いました!

Dockerに手を出しても理解出来るか?

本当に初心者だとDockerの学習は結構ハードルが高いと思いました。。。 Dockerを学ぶ時に障害となりそうなことを考えてみました。

  • 基本的には、CUI操作なので、GUIに慣れている人には少しつらい
  • 基本的なコンテナ内の操作はLinuxなのでLinuxの知識が0だとつらい
  • 立てることが出来るのと、実際に操作出来るのは違うのでミドルウェアフレームワークの知識は必要

Dockerで構築するにしても、インフラ周りの基礎知識やミドルウェア、フレームといった機能を学ばなくて良いというわけでは無いですね。。。

自分でWebサービスをつくってみて、構成の概要等がつかめてからDockerを勉強してみると理解しやすいのでは無いかと思いました!

ちなみにプログラミングを始めたばかりの人は、下手に自分で環境を立てずにCloud9等のオンライン実行環境がオススメです!!

aws.amazon.com

paiza.io

おわりに

Dockerはトレンドの技術になっていて、エンジニアとして働けるようになったら、最新の技術を試す環境を簡単に構築出来たり、環境構築に掛かる時間を短縮出来たりとメリットが多そうなので、インフラ周りは苦手意識があるのですが、きちんと理解してスキルとして使えるようになりたいですね・・・!!

冒頭で紹介した本は、プログラマのためのという記載もあるように、インフラやLinuxの基礎的な部分の説明からDockerの説明に入るので、プログラミングを学習してWebサービスを作ってみて、構成の概要等がつかめてきた人がDockerを学ぶ入門書にちょうど良いかなと思いました!:D

皆様の良ければ、読んでみてくださいφ(..)

Ruby:ppモジュールで実行結果を良い感じに確認する

はじめに

Rubyでデバック実行する時に、よくpメソッドを使うと思いますが、Rubyには標準でppモジュールというものがあります。

ppモジュールを使うとpよりも良い感じで実行結果を確認出来ます!

使い方

導入はとても簡単で、ソースコードrequire 'pp'と記載するだけです!

ソースコード

require 'pp'

users = [
  {name: "太郎", age: 20},
  {name: "花子", age: 20},
  {name: "聡美", age: 20},
  {name: "庄司", age: 20}
]

p users
pp users

実行結果

# pの結果
[{:name=>"太郎", :age=>20}, {:name=>"花子", :age=>20}, {:name=>"聡美", :age=>20}, {:name=>"庄司", :age=>20}]

# ppの結果
[{:name=>"太郎", :age=>20},
 {:name=>"花子", :age=>20},
 {:name=>"聡美", :age=>20},
 {:name=>"庄司", :age=>20}]

ppの方が良い感じに改行が入って見やすいですね!:D APIから取得したjsonを確認する時とか、色々と役に立ちそう。

参考

docs.ruby-lang.org

DockerでRubyonRailsの環境を構築してみた

f:id:madogiwa0124:20180114153719p:plain

はじめに

Dockerの勉強会に参加して少し、Dockerについて勉強しました。

tec.connpass.com

せっかく勉強したので、Raisの開発環境をDockerで構築してみました(._.)

まだまだわからないことだらけですが、とりあえず開発環境をDockerコンテナ上に構築・実行確認とherokuへのデプロイまで確認出来たので、手順をメモしてみますφ(._.)

※ちなみに下記ページを大変参考にさせて頂きました・・・!

qiita.com

DockerFile等を含んだ形でgithubサンプルソースをpushしているので、ソースだけ見たい方はこちらへ。

github.com

環境

私の環境

ちなみに私のローカル環境は、こんな感じです!

構成 内容
ローカル Mac OSX Yosemite
Docker 17.12.0-ce-mac47

Docker

Dockerとは

DockerとはRubyやPosgresといったミドルウェアをDocker上にコンテナとして構築出来るものです。

似たような技術として仮想環境がありますが、下記のようなメリットがあり、最近注目されている技術です。

  • 環境をテキストベースのファイルとして配布出来る。環境の再現性。
  • スクラップ&ビルドが容易
  • DockerがOSレイヤーをラップして、各OSの差分を吸収してくれるためOS環境を気にしなくても良い。

上記説明ではわからない部分が多いと思うので、下記のような記事で確認頂ければと思います。

employment.en-japan.com

Dockerのインストール

MACだったらDockerのインストールはとても簡単です!

下記のインストールページにアクセスし、Get Dockerを押すと安定版のインストーラーが入手出来るので、あとは手順に従えばOKです。 https://store.docker.com/editions/community/docker-ce-desktop-mac

f:id:madogiwa0124:20180114152439p:plain

最後にターミナルからインストールを確認して下記のようになれば、OKです。

$ docker --version
Docker version 17.12.0-ce, build c97c6d6

Dockerを使ってRails開発環境を構築する

目指す環境構成

今回目指す環境はこんな感じで、ベーシックなRailsの環境を目指して行きます!

構成 内容
Ruby 2.4.0
Rails 5.1.4
DB PostgreSQL 10.1
本番環境 Heroku

Docker上にRailsが動作するコンテナとPosgreSQLが動作するコンテナの立てて、ローカルで動作確認を行いHerokuへデプロイしてみたいと思います。

イメージはこんな感じです。 f:id:madogiwa0124:20180114152357p:plain

手順

Railsプロジェクトの作成

まずは、普通にローカルでRailsプロジェクトを作成します。

rails new docker_sample_app

Docker関連ファイルの配置

その後、DockerFile及びdocker-compose.ymlをrootディレクトリ配下に配置します。

DockerFileはコンテナの定義ファイルで、docker-conpose.ymlは、DockerFileの関連を定義したファイルです。

DockerFileでWeb用のコンテナを既存のrubyのimageをベースに定義しています。DB用のコンテナはdocker-compose.ymlで公式のpostgresのイメージを使ってそのまま構築しています。

DockerFile

# ベースイメージの設定
FROM ruby:2.4.0
# 環境変数の設定
ENV APP_ROOT /docker_sample_app
# コンテナ上で関連パッケージのインストール
RUN apt-get update -qq && apt-get install -y nodejs build-essential libpq-dev postgresql-client
# コンテナ上でフォルダを作成
RUN mkdir $APP_ROOT
# 作業ディレクトの設定
WORKDIR $APP_ROOT
# Gemfileをコンテナ上にコピー
ADD Gemfile ${APP_ROOT}/Gemfile
ADD Gemfile.lock ${APP_ROOT}/Gemfile.lock
# コンテナ上でbundle install
RUN bundle install
# rootフォルダ配下をコンテナ上にコピー
ADD . $APP_ROOT

docker-compose.yml

version: '2'
services:
  # DB用のコンテナ
  db:
    image: postgres
    ports:
      - "5432"
    # データをホスト側で保持し、関連付けることで永続化
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
  # Web用のコンテナ
  web:
    # DockerFileの配置フォルダ
    build: .
    # コンテナ起動時に実行されるコマンド
    command: bundle exec rails s -p 3000 -b 0.0.0.0
    ports:
      - "3000:3000"
    # dbとwebを関連付け
    links:
      - db
    # ローカルのルートディレクトリをコンテナ上の作業ディレクトリと関連付け
    volumes:
      - ./:/docker_sample_app
    # 環境変数
    environment:
      DATABASE_USER: postgres
      DATABASE_PASSWORD:
      DATABASE_PORT: 5432
      DATABASE_HOST: db

DB関連ファイルの修正

DBをPosgreSQLに変更

これは、過去の記事にまとめているので参照して頂ければと思います。

madogiwa0124.hatenablog.com

DB設定をDB用のコンテナに合わせて変更

DockerコンテナでDBを構築するにあたって、DBの参照の仕方等を少し工夫する必要があるためにdatabase.ymlファイルを編集します。

default: &default
  adapter: postgresql
  encoding: unicode
  timeout: 5000
  # docker-composeで指定した環境変数の読み込み
  port: <%= ENV.fetch('DATABASE_PORT') { 5432 } %>
  host: <%= ENV.fetch('DATABASE_HOST') { 'localhost' } %>
  username: <%= ENV.fetch('DATABASE_USER') { 'root' } %>
  password: <%= ENV.fetch('DATABASE_PASSWORD') { 'password' } %>
  pool: <%= ENV.fetch('RAILS_MAX_THREADS') { 5 } %>

development:
  <<: *default
  database: docker_sample_app_development

test:
  <<: *default
  database: docker_sample_app_test

production:
  <<: *default
  database: docker_sample_app_production
  username: docker_sample_app
  password: <%= ENV['appname_DATABASE_PASSWORD'] %>

Dockerコンテナの起動

では実際にDocker上に環境を構築していきます。 流れとしては、下記のような形になります。 1. Web用のコンテナとDB用のコンテナを構築 2. DBを作成 3. マイグレーションの実行 4. Dockerコンテナの起動

# Dockerイメージの取得等の初期化処理
$ docker-compose build
# DBの作成とマイグレーションの実行
$ docker-compose run web rake db:create
$ docker-compose run web rake db:migrate
# Web、DB用のコンテナの起動
$ docker-compose up

localhost:3000にブラウザから接続するとRailsのスタートページが見えました!

f:id:madogiwa0124:20180114152508p:plain

Herokuにデプロイ

最後にHerokuにデプロイしてみようと思います。 HerokuへのデプロイはDockerを使わない環境と特に変わりません。

今回は、scaffoldUserモデルを作成後、Herokuへデプロイしてみました。

$ rails generate scaffold user name:string age:integer
$ git add -A
$ git commit -m "scaffold user model"
$ git push heroku master
$ heroku run db:migrate
$ heroku open

無事にherokuにデプロイできました!
https://rails-docker-sample-app.herokuapp.com/users

f:id:madogiwa0124:20180114152524p:plain

おわりに

まだまだDockerのことは理解出来ていない部分が多いですが、なんとかネットの情報を元に環境と立ててデプロイすることが出来ましたφ(..)
Dockerだと失敗しても潰して、もう一回ということが簡単に出来るので、仮想環境の構築よりもストレスは大分少ないんじゃないかなぁと思います…!

Dockerは色々書籍も出ているので、色々と勉強してみようと思いました。 オススメの書籍等あれば、教えてくださいφ(..)

参考

tech.recruit-mp.co.jp

qiita.com