20191214のRubyに関する記事は14件です。

rubyの機能を活用し、a~zを繋げた文字列を取得するために冗長に書く

***注意***

以下のコードは かなり冗長 となっています。
「これ(a..z).to_a.joinでええやん^^;」はなしでお願いします。

本題

最近友人にRubyを教えていて、これが読めればいいんじゃないかな?ってコードを考えてみたところいい感じのが出来たので置いときます。

コード

# 可読性上げるためインデント調整してます。
__send__(
  define_method(
    25.times
      .inject(false.to_s[1]) { |s| s + s[s.size.pred].next }.to_sym,
    -> { __method__.to_s }
  )
)

pryでの実行結果

スクリーンショット 2019-12-14 20.45.18.png

ワンライナーでの実行

スクリーンショット 2019-12-14 21.24.47.png

解説

コード貼り付けただけだとしょっぱいので、内側から解説していきますw

25.times

define_methodの第一引数であるメソッド名(シンボル)を作成していきます。
25.timesEnumeratorオブジェクトを作り、自身をレシーバとしたEnumerableメソッドの実行回数を決めています。
これにより、inject25回実行させることができます。

inject(false.to_s[1]) { |s| s + s[s.size.pred].next }.to_sym

ここで(a..z).to_a.join.to_symと同様のコードとなるようにします。

引数に'a'を与え、ブロックの中で「現在の文字列 + (現在の文字列の最後の文字)の次の文字」を行なっています。

# 中身はこのイメージ
inject('a') {|'a'| 'a' + 'b'}
# 'a'の次は'b'   
inject('ab') {|'ab'| 'ab' + 'c'}
# 'b'の次は'c'
# ..., zを取得するまで繰り返す

これにより、次の文字を取得してくっつける処理が25回実行されるためa~zが得られます。

最後にto_symでシンボル化し第一引数は完成です。

-> { __method__.to_s }

difine_methodへ渡す第二引数であるProcオブジェクトを作成しています。
__method__でこのメソッドを呼び出したメソッド名(シンボル)を取得し、Stringオブジェクトに変換します。

define_method(name, proc)

第一引数にa~zを繋げた文字列のシンボル、プロックを渡しレシーバへメソッド定義をさせます。
そのままでは定義するだけで終わるので何かしらのメソッドで実行させます。

__send__(symbol)

レシーバに対して引数のメソッド名を実行します。
define_methodの戻り値が定義したメソッド名のシンボルであることを利用しそのまま渡します。

結果として-> { __method__.to_s }が呼ばれ、ここでa~zの文字列が得られました。

他の手段ですが、method(...).()でも同じようにできます。
この辺は好みだと思うのでどちらでも大丈夫ですねw

まとめ

解説は以上です。
このコードでは簡易的ですが、メソッドチェーン、メタプロ、リフレクション、Enumerable、Procオブジェクトが登場します。
脱初心者を目指すにはいい足掛かりにはなるんじゃないでしょうか?(主観100%)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

懇親会での障害対応はもうこりごり!�JMeterを使ったRailsアプリの負荷テストの流れ

これは Money Forward Advent Calendar 2019 ? 14日目の記事です。

こんにちは! @machisukeです。
マネーフォワードでは、半年に1回全社員集まっての半期総会を開催しています。
そして昨日、2019年12月13日がちょうど半期総会でした。

半期総会後の全社懇親会 :beers: で、僕たちマネーフォワード新卒はあるリベンジを果たそうとしていました :fire:

もうサーバーは落とさない。

今年6月に開催された半期総会で、僕たちは懇親会のコンテンツとして「MFクイズダービー」を担当しました。

スマホを使ってリアルタイムに順位が発表されるという内容に大盛り上がりでコンテンツはスタート。
会場の盛り上がりを見た僕たちは、作った甲斐があったなあと安堵していました。しかしその直後に事件は起きます。300名を超える参加者にサーバーが耐えきれず、途中でシステムが止まっていたのです。詳細は弊社のエンジニアブログ「新卒が社内懇親会アプリを開発したら、障害対応まで経験できた話」をぜひ読んでみてください。

このままでは終われないと、半年後の全社懇親会でのリベンジを心に決めました。

リベンジの過程で、僕は負荷テストを実施し、その時使った「JMeter」がとても便利で面白かったので、皆さんに手順を共有したいと思います。
(※ちなみに、今回の懇親会が成功したのかどうかは誰かがブログを書くと思うので楽しみに待ちましょう。)

JMeter上でのテスト計画と結果のイメージ

このような感じで、JMeterを使ってクイズの参加登録(sign_up)、クイズ取得、クイズ回答などが正しく動作しているか検証できます。本番は50チームで行いますが、テストは300チームで行いました。
image.png

image.png

Railsアプリの負荷テストに挑戦してみよう :rocket:

今回は負荷テストを検証するアプリケーションとしてRailsチュートリアルで作成するSampleAppを拝借したいと思います。
SampleAppはTwitterのように「Micropost」を投稿するサービスです。

30ユーザーを同時アクセスさせ、1秒あたり1投稿させても、アプリは落ちることなく動き続けるでしょうか!?

SampleAppの画面 :point_down:
image.png

環境

  • Mac OS Mojave
  • JMeter 5.2.1
  • ruby 2.6.5
  • rails 5.1.2 (sample_appの最新版に合わせました)

手順

1. jmeterインストール

$ brew install jmeter

2. Railsアプリケーションの起動

$ git clone https://github.com/yasslab/sample_apps.git
$ cd sample_apps/5_1_2/ch14
$ bundle install
$ bundle exec rails db:create
$ bundle exec rails db:migrate

今回はメール認証を強制的にスキップするため、app/controllers/users_controller.rbに変更を加えます。
①、②の変更を行ってください。

app/controllers/users_controller.rb
  # POST /users
  def create
    @user = User.new(user_params)
    if @user.save # => Validation
      # Sucess
      # ①↓コメントアウト
      #@user.send_activation_email
      # ②↓追加
      @user.activate        
      flash[:info] = "Please check your email to activate your account."
      redirect_to root_url
    else
      # Failure
      render 'new'
    end
  end

起動

$ bundle exec rails s

http://localhost:3000 にアクセスすると画面が開かれるはずです。

3. JMeter起動

$ jmeter

image.png

HTTP Request Defaults作成

Test Plan 右クリック > Add > Config Element > HTTP Request Defaults
起動しているサーバーのアクセス情報を入れます。

image.png

Thread Gropu(ユーザーグループ)の作成

Test Plan 右クリック > Add > Threads (Users) -> Thread Group
同時にアクセスするユーザー数を適当に決めます。
今回は、30人のユーザーが30秒の間に操作を開始するという設定にします。

image.png

ユーザ毎に登録内容を変える準備

i番目のユーザーは

name: name_i
email: name_i@example.com
password: password_i

としましょう。

Test Plan 右リクック > Add > Config Elemennt > Counter
coutnerという変数名で取得できるようにします。

image.png

4. JMeterでユーザー登録、ログインさせる

ユーザー登録・ログインのリクエストをグルーピングする

Thread Group 右クリック > Add > Logic Controller > Simple Controller

名前はsign_up/sign_inにします
image.png

ユーザー登録(sign_up)フォームの取得

sign_up/sign_in 右クリック > Add > Sampler > HTTP Request

名前はsign_upフォーム取得にします。
sign_upフォームが表示されるURLは
http://localhost:3000/signupなので、Pathにsignupを入力します

image.png

正しくリクエストできているか検証

Test Plan 右クリック > Add > Listener > View Results Treeを追加

JMeterの上側の緑色の三角ボタンを押してテストをスタートするとリクエスト結果が出ます。

image.png

AuthenticityTokenの取得

今回使うRailsアプリはCSRF対策が施されているので、AuthenticityTokenをリクエストパラメーターに含める必要があります。
AuthenticityTokenは、「登録フォーム取得」のレスポンスに含まれています。

これは、Regular Expression Extractorで抜き出します。

sign_upフォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。

image.png

ユーザー登録

sign_up/sign_in 右クリック > Add > Sampler > HTTP Request
名前はsign_upにします。
urlencodeにチェック入れるのを忘れないようにしましょう。

image.png

ログイン状態を保持できるようにする(Cookie)

Test Plan 右クリック > Add > Config Element > HTTP Cookie Manager

追加するだけでOKです。

image.png

ログイン

登録同様、下記の手順を行います。

  1. フォーム取得
  2. authenticity_token抜き出し
  3. ログイン

sign_up/sign_in 右クリック > Add > Sampler > HTTP Request
名前はsign_inフォーム取得にします。
image.png

sign_inフォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。

image.png

sign_up/sign_in 右クリック > Add > Sampler > HTTP Request
名前はsign_inにします。
image.png

試しにログインしてみる

ブラウザでhttp://localhost:3000/loginを開き、適当なユーザーでログインして、http://localhost:3000/usersにアクセスしてみる。

ユーザーが作られているのがわかりますね。
image.png

※テストを実行すると、ユーザーが登録されてDBに保存されます。
テストの度にDBをリセットするとユーザー登録から正しくテストを行うことができます。

$ bundle exec rails db:migrate:reset

5. 各ユーザーに、Micropost(Tweet)を50件登録させる

sign_in/sign_up同様、下記の手順でMicropostを投稿します。

  1. フォーム取得
  2. authenticity_token抜き出し
  3. 登録
投稿リクエストをグルーピングする

Thread Group 右クリック > Add > Logic Controller > Simple Controller

名前はMicropost投稿にします
image.png

さらに
Thread Group 右クリック > Add > Logic Controller > Loop Controller

名前は50回投稿にします

image.png

投稿ごとにメッセージを分けるための変数を用意

50回投稿 右リクック > Add > Config Element > Counter
micropost_coutnerという変数名で取得できるようにします。

image.png

投稿フォーム取得

50回投稿 右クリック > Add > Sampler > HTTP Request
名前は投稿フォーム取得にします。
URLはhttp://localhost:3000なので、pathは何も入力しません。

image.png

AuthenticityToken取得

投稿フォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。

image.png

投稿

50回投稿 右クリック > Add > Sampler > HTTP Request
名前は投稿にします。

image.png

投稿間隔の調整

50回投稿 右クリック > Add > Timer > Constant Timer
投稿間隔を一人につき、1秒1回に調整します。

image.png

6. Listener(レポート機能)の設定

テストが終わるまでのレスポンスタイムの遷移を見る

Test Plan 右クリック > Add > Listener > jp@gc - Response Times Over Time

7. テスト実行

JMeterの上部にある、緑色の三角ボタンを押したら始まります。

8. テスト結果

サーバは落ちませんでした :tada:
ただ、ところどころピークが生まれていて、ログを見るとDBのRollbackが行われている様子。同時書き込みに弱いSQLiteだから発生したRollbackでしょうか・・?

image.png

まとめ

JMeterは気軽に負荷テストを行えるツールでした。

どのようにインフラ/実装を変えれば、レスポンスタイムが短くなるかを考えてみるのは、課題として面白そうですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでクエリが複雑になってきたのでQueryObjectパターンでリファクタをして良かった話

この記事は Opt Technologies Advent Calendar 2019 13 日目の記事です。若干遅刻しました。

12 日目の記事は @gcchaan さんの なにか書きます です
14 日目の記事は @naru0504 さんの styled-componentでモダンなCSS設計入門 です


「RailsでActiveRecordでクエリが複雑でつらいしパフォーマンスも悪くなってきた」
よくある話だと思います。例に漏れず、自分のチームも同じ問題と向き合いました

クエリが複雑になりアプリケーションの修正時にコードリーディングが難しい・修正後の結果予測が困難になるという、まあ言ってしまえば典型的な事例です
これまでに同様の議論は多くなされてきたと思いますが、自分のチームがこの問題と向き合い、「何が問題で・どのように解決し・結果どうなったか・反省点」という観点で事例を書き残しておこうと思います

何が問題だったか

プロダクトにとっての問題点

ユーザーに利用してもらうにはあまりにも レスポンスが遅い という状態になってしまっていたことが第一です
プロダクトにとってとても優先度の高い機能を優先して実装していたのですが、それがリリースされたときにはもうだいぶ遅い(レスポンスに30sec以上平気でかかる・・・)状態になっていました

コードベースの問題点

クエリについて話しているのでお察しのことと思いますが、SQLがボトルネックでした
しかし、クエリを改善しようとするも、以下の状況が立ちはだかります

  • プロダクトの性質上、集計関数を用いたクエリが必要
  • 基本方針として、よく使うscopeを実装し、その組み合わせによってクエリを実現していた
    • レポーティングのクエリで、同じテーブルに対して複数の結果セットが欲しいので、とあるscopeが複数のクエリ呼び出しから参照されていた
  • scopeは別のscopeも参照していた
  • scopeの組み合わせるクエリ組み立て処理はControllerで書いていた
  • 一部でクエリの結果に対しての複雑な加工処理もあった(クエリ組み立てがControllerで実施されているので、この処理もControllerで呼び出しされていた)
  • Controllerで呼び出されている処理について直接のテストはなかった
    • あったのはrswagによるスキーマのテストぐらい
  • 各scopeは結構丁寧にテストは書かれていた

イメージとしては以下のような実装をもっとFatにしたものになっていました

foo_controller.rb
class FooController < ApplicationController
  def index
    # Controllerでこのようにscopeチェインしてクエリを組み立てていた
    @foo = Foo.by_foo(params[:some_param])
      .of_baz(params[:some_condition], params[:some_condition2])

    render json: @foo
  end

  def other
    # 別のメソッドやControllerなどからも呼ばれることもある
    @foo_other = Foo.of_baz(params[:some_condition], params[:some_condition2])
      .by_other
    render json: @foo_other
  end
end
foo.rb
class Foo < ApplicationRecord
  scope: by_foo, ->(some_param) {
    # このように別のscopeを参照している     ---↓
    select(:foo).group(:foo).order(:foo).sum_bar
  }
  scope: sum_bar, ->() {
    select('SUM(bar) AS bar')
  }

  scope: of_baz, -> (cond1,cond2) {
    baz_condition = make_condition(cond1,cond2)
    where(baz: baz_condition)
  }
end

まとめると、でかいクエリが詳細なテストなしに複数あって、scopeの参照関係も複雑、という形です

どのように解決したか

タイトルにも記載しましたが、QueryObjectパターンを利用しました

QueryObjectについて参考にした資料
- 7 Patterns to Refactor Fat ActiveRecord Models
- Rails - ActiveRecord の scope を Query object で実装する

ただ適当にQueryObjectを利用しても上手く行かないと思い、以下のような指針でQueryObjectへの切り出しを実施しています

実装の方針

  • 「最終的に欲しいクエリ」は必ずQueryObjectに定義する
  • 実装自体は引き続きscopeのチェイン
  • ただし、scope内から別のscopeを(なるべく)参照しない
    • 結果の予測が困難になる理由の一つだったため
    • scopeの利用自体は許容(scopeを利用しないとQueryObject側の実装が複雑になりそうだったため)
  • 「最終的に欲しいクエリ」同士で重複しているクエリは許容
    • scopeという「クエリの断片を組み合わせる処理」を呼び出す部分を共通化するのは「早すぎる抽象化」になりそう

以上のような方針にすることで、以下のような恩恵を受けられます

  • 最終的なクエリに対してのテストがQueryObjectの呼び出しだけで実施できるようになる
  • scopeの影響範囲が明快になり、変更の結果を予測しやすくなる・影響範囲を限定できる

リファクタの方針

方針を決定したので、リファクタをします
といってもこれ自体は「まずはテスト出来るような形にだけ変更」、「テストを書く」、「リファクタを実施」という鉄則に従って実施しただけです
幸いだったのが、「まずはテスト出来るような形にだけ変更」が非常に容易だった点です

先程の例を元に説明します

  • 最初に FooController#index についてのみをQueryObjectに切り出す
foo_query.rb
class SumOfFooQuery
  class << self
    delegate :call, to: :new
  end

  def initialize(foo = Foo.all)
    @foo = foo
  end

  def call(params)
    # ここにFooController#indexに書かれていた処理をまるっとコピペ
    @foo.by_foo(params[:some_param])
      .of_baz(params[:some_condition], params[:some_condition2])
  end
end
  • モデルにQueryObjectを参照したscopeを定義
foo.rb
scope :sum_of_foo, SumOfFooQuery
  • FooControllerの実装を置き換え
foo_controller.rb
class FooController < ApplicationController
  def index
+   @foo = foo.sum_of_foo(params)
-   @foo = Foo.by_foo(params[:some_param])
-     .of_baz(params[:some_condition], params[:some_condition2])

    render json: @foo
  end

  def other
    @foo_other = Foo.of_baz(params[:some_condition], params[:some_condition2])
      .by_other
    render json: @foo_other
  end
end

  • テストを書く
  • リファクタする
    • サンプルは割愛

以上のような流れでリファクタリングを順次実施していきました

結果どうなったか

クエリ単位でのテストがあり、影響範囲も狭められたので、特定のエンドポイントから順番に・独立してクエリチューニングを実施できるようになりました
(チューニングはそれはそれで大変だったのですがそれはまた別の話)

scope内から別のscopeを(なるべく)参照しない「最終的に欲しいクエリ」同士で重複しているクエリは許容 といった方針も見込み通りにコードの読みやすさや変更しやすさに繋がったという感触です

その後運用していても、変更時に大きく困るような事態にはなっていないので、設計は上手くいったと思っています

反省点

  • Controllerに処理書いちゃだめだった
    • ここを徹底すべきだった
  • scopeの先のscopeの先のscope・・・という道のりを辿った先でのselectを把握してコーディングするのは人間には無理だった
    • 作るときはいいけど変更できない
    • 「1つのメソッドを短くする」、「DRY」を徹底すればいいってもんじゃなかった
      • メソッドの定義をバラけさせればバラけさせるほど「結局何をやりたいのか」が分かりにくくなることもある
      • (かと言ってまとめりゃいいってもんでもないので難しい)
  • レスポンスに30sec以上という状態は流石にもっと早く手を打てたんじゃ・・・
    • turai
    • 開発が進むうちにデータが溜まって表出したものが多かったので、開発初期時点でデータを作れるなら作っておくという選択肢は今後頭に入れておきたい
    • 機能追加の優先度が高かったので、パフォーマンスがヤバくなるかもしれないとわかってても優先度を変更するかは判断が難しかったと思う
      • ので、普段から変更に強い設計にしておく・設計を身に着けておくという再現性のない反省になってしまう・・・

ただ、以下のような良かった点もあって、この前提がなかったらもっと困難な課題になっていたと思います

  • scopeで非常に汎用的なクエリを表現していた
    • 例えば見込み値の算出クエリなど
    • このような複雑な処理がControllerに氾濫していたらQueryObjectへの移行が困難だったと思う
  • scope単位のユニットテストはあった
    • テスト大事・・・
  • Controllerはクエリ組み立てとrender用の多少の加工だけでFatにはなっていなかった
    • Fatになる前にちゃんとリファクタに手を付けられたとも言えるかも

おわりに

ということで、Railsでクエリが複雑になってきたのでQueryObjectパターンでリファクタをして良かった話でした
今回の事例がどこかのRailsプロダクトの参考になれば幸いです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分用 Ruby on Rails 初歩メモ

Rubyの要注意基本文法

名前付きルートの使い方

  1. 対象のルーティングの末尾に ‘as HOGE’ を追記
  2. View で対象のルーティングに遷移するリンクのURLにて’HOGE_path’ と指定
  3. もし、対象のルーティングにパラメータを渡す際は、 ‘HOGE_path(x)’ の表記になる。

インスタンス変数とローカル変数

@hoge : インスタンス変数、ビューに渡せない
hoge : ローカル変数、ビューに渡せる

コントローラの基本7アクション

  • new データを新規作成する
  • create データを追加(保存)する
  • index データの一覧を表示する
  • show データの内容(詳細)を表示する
  • edit データを更新するためのフォームを作成する
  • update データを更新する
  • destroy データを削除する

View で利用するタグについて

<% %> では、タグ内の結果が画面に表示されずに処理される。
<%= %> では、タグ内の結果が画面に表示される。

form_for における POST, PATCHの判定

form_forにおけるsubmit時のメソッドについて、特にPOST, PATCHから適切なものが指定される。
これは、コントローラから渡されたオブジェクトがEmptyかNotEmptyかによってActiveRecordのnew_record?メソッドは自動で判別してくれているため。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Runteqに入ってから意識的に変わったこと

はじめに

私は10月から渋谷のRunteqというスクールに通っています。
それまでに開発の実務経験はなく、rails tutorialを3ヶ月ほど独学で勉強していました。
Runteqに入ってから2ヶ月ちょっと経ち、技術的な事はもちろんですが、その他に意識的に変わったなと思うことがあるのでご紹介します。
開発経験者の方からすると当たり前と思うこともあるかもしれませんが、温かい目で見て頂けると嬉しいです。

対象者

初学者の方、特に独学でrailsを勉強されてる方

公式ドキュメントを読むようになった

Qiitaの記事に書いてある通りに実行してるのに上手くいかないという事は皆さん経験があると思います。記事の通りにならないのは、記事を書いている人と環境、特にバージョンが異なっている場合が多く、そのような時は公式ドキュメントが非常に参考になります。
(その他にも、勉強し始めの頃から公式ドキュメントを読む利点として、今後必要な情報が全てqiitaにわかりやすくまとめられているわけではないために公式ドキュメントを読む練習をするというのがあると思います。)

railsの場合、githubが公式ドキュメントとなっていることが多く、例えばsorceryというgemだと<>CodeタブやWikiタブを見て使い方を理解します。
説明の代わりにソースコードが載っている事もあり、察する力も大事なのかなと思います。rails tutorial等をやっていたら、理解もずっと早くなりますよ。

大体英語で書かれているため、分かりづらい時はgoogle翻訳や、補助的にQiitaを使ったりするのがオススメです。

新しいツールに手を出すようになった

Runteqの教室内やslackにて、意見交換をしたり、オススメのツールが紹介される事がよくあります。
スクールに入ったのをきっかけに、色々経験してみようと思い、紹介されたツールを全部試していました。
以前の自分は、ツールをインストールするより目の前の作業に時間を使いたいという思考をしていました。実際本格的なツールになるほど複雑な使い方を覚えなくてはいけないというのはあると思うのですが、だからといって躊躇っていると今後使うツールの幅がぐっと狭くなり、結果効率化から遠のいてしまいます。
使ってる数が多いほど良いというものではありませんが、自分と合うツールは積極的に取り入れていきましょう。日々の業務が効率的になります。

自分のオススメのツールを書いてみます。色々試してみてください。

  • google keep:タスク等をメモするのに使っています。タグ付けもできるので便利です。
  • HyperSwitch:アプリ切り替えの際、ウィンドウ単位で切り替えられるようになります。
  • Magnet:Macでの画面分割が楽にできます。
  • cvim:vimの操作でchromeを閲覧できます。vimを使っている方は是非。
  • rubymine:rubyの便利機能が詰まっているエディタです。

ショートカットコマンドを覚えるようになった

Runteqにて質問をする時にいつも思うのですが、講師の方はPCの操作が早いのです。
ショートカットコマンドを多用しているのがその要因の一つではないかと思い、ショートカットコマンドを覚えようと意識してみました。

覚えるのはなかなか大変ですが、2つ〜3つずつくらいを目安にちょっとずつ覚えていくと段々使えるようになってきます。
また、自分だけかもしれませんが、ショートカットを使えるようになると、操作が早くなるだけでなく、使ってて楽しく感じます。

自分が特に使えると思ったショートカットコマンドをいくつかご紹介します。
使い慣れていない方は、まずはこれらから試してみて、使いこなせるようになってきたら新しいショートカットコマンドを模索してみてください。

chrome

command + tab:アプリケーションの切り替え(shiftを押しながらだと逆向きに切り替え)
control + tab:タブを1つ右に切り替え(shiftを押しながらだと1つ左に切り替え)
command + [:ページを1つ戻る( ]だと1つ進む)

vimを使っている方であれば、cvimをインストールして、上記ショートカットと組み合わせるとより快適になります。

ターミナル

control+a:カーソルを一番左に移動
control+e:カーソルを一番右に移動
control+l:画面をクリア
control+u:カーソルより左を削除
control+k:カーソルより右を削除

まとめ

「Runteqに入ってから意識的に変わった事」として紹介しましたが、今考えるとどれも独学の頃から意識できた事だと思います。
独学の頃は目の前の作業に夢中になっていましたが、少しゆとりを持って手を広げてみるともっと日々のエンジニア生活が楽しくなると思うので、是非色々試してみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows Subsystem for Linuxでmysqlを使えるようにする。

環境

Windows 10 pro
Ubuntu 18.04 LTS
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
Rails 5.2.4
gem 2.7.6
Bundler version 2.0.2

mysqlの前に

github上で

$ rails _5.2.4_ new example -d mysql

をして新しい環境を作った後、
Gemfileを

gem 'mysql2', '0.5.2'

と編集されたものを、
git pullした後に、
WSL上でbundle installしようとしたが、

$ bundle install

  * * *

Fetching mysql2 0.5.2
Installing mysql2 0.5.2 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

  * * *

-----
mysql client is missing. You may need to 'apt-get install libmysqlclient-dev' or 'yum install mysql-devel', and try again.
-----
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

  * * *

An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  mysql2

とエラーが出てしまった(汗)

-----
mysql client is missing. You may need to 'apt-get install libmysqlclient-dev' or 'yum install mysql-devel', and try again.
-----

この箇所を参考に

$ sudo apt-get install libmysqlclient-dev

した後、

Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds before bundling.

この箇所を参考に

$ gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'

Successfully installed mysql2-0.5.2
Parsing documentation for mysql2-0.5.2
Installing ri documentation for mysql2-0.5.2
Done installing documentation for mysql2 after 0 seconds
1 gem installed

こうすると、

$ bundle install
$ bundle update

がそれぞれ実行できて、gemの環境が整った(^^)/

mysqlを使えるようにする

試しにmysqlの状態を確認すると、

$ mysql
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

mysqlをstartさせようと試みた後、

$ sudo service mysql start
 * Starting MySQL database server mysqld                                                                                                                           
No directory, logging in with HOME=/

もう一度、mysqlの状態を確認すると、

$ mysql
ERROR 1045 (28000): Access denied for user 'example'@'localhost' (using password: NO)

こちらのサイトによると、
パスワードを設定しなおさなければならないらしい。

$ sudo mysqld_safe --skip-grant-tables &

セーフモードで実行したのち、

$ sudo mysql -u root
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 5.7.28-0ubuntu0.18.04.4 (Ubuntu)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

mysql> show databases; 
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.00 sec)
// データベースの状態を確認したよ

mysql>  update user set authentication_string=password("パスワード") where user='root';
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
// パスワードは英数字と文字を組み合わせないとだめですよ

mysql> update user set authentication_string=password("英数字と文字をいれたパスワード") where user='root';
Query OK, 1 row affected, 1 warning (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 1
$ sudo service mysql start
 * Starting MySQL database server mysqld     
$ mysql --version
mysql  Ver 14.14 Distrib 5.7.28, for Linux (x86_64) using  EditLine wrapper

こういう状態になりました(^▽^)/

素人がやっているので説明不足等あるとは思いますが、
ご指摘の程、よろしくお願いします!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【トリビア】Rubyにおける3連引用符(ダブルクオート3つ)は何を意味しているのか?

はじめに:Rubyクイズ!!

Rubyプログラマのみなさん、こんにちは。
突然ですが、問題です。
以下のコードを実行すると何が起きるでしょうか?

sample.rb
msg = """
Hello, world.
I am Ruby.
"""

puts msg

はい、実行結果はこうです。

$ ruby sample.rb 

Hello, world.
I am Ruby.

どうですか?想像通りの結果になりましたか?
それではまた。ごきげんよう。

・・・ではなくて、上のコードに出てきた3連引用符(""")ってみなさん知ってましたか?
Rubyを長年やってる人もあまり見たことはないんじゃないでしょうか。
というか僕も最近知りました。

""" の種明かし

「Rubyに """ なんていう構文はあったっけな〜??」と思ってネットや書籍を調べても、たぶんそんな構文はどこにも載っていないと思います。

これ、実は""" = "" + "の意味になっているだけなんです。
つまり、先ほどのコードはこう書いているのとほぼ同じです。

msg = "" + "
Hello, world.
I am Ruby.
" + ""

結果として、こう書いていることと同じになります。

msg = "
Hello, world.
I am Ruby.
"

ウソだ!と思っている人も、putsの代わりにpを使って確認すると、納得がいくかもしれません。

msg = """
Hello, world.
I am Ruby.
"""

p msg
#=> "\nHello, world.\nI am Ruby.\n"

公式リファレンスの説明を確認する

公式リファレンスにも以下のような説明が載っています。

リテラル (Ruby 2.6.0 リファレンスマニュアル)

空白を間に挟んだ文字列リテラルは、コンパイル時に1つの文字列リテラルと見倣されます。

p "foo" "bar"
=> "foobar"

リファレンスには「空白を間に挟む」とありますが、空白なしでも1つの文字列になるようです。

p "foo""bar"
#=> "foobar"

よって、

"""abc"""

は、

"" "abc" ""

と書いていることになり、結果として、

"abc"

と書いたことになるわけです。

ちなみに、シングルクオートで書く場合も考え方は同じです。

p 'foo' 'bar'
#=> "foobar"

p '''abc'''
#=> "abc"

余談:C言語も同じ文法を持っています

「隣り合った文字列リテラルは1つの文字列として扱われる」という文法は、C言語でも同じだそうです。
というか、C言語の影響を受けているとこういう文法が用意される、と言った方が正しいかもしれません。

#include <stdio.h>

int main(void) {
  // 実行すると"Hello, World!"になる
  printf("Hello, " "World!\n");
  return 0;
}

参考 https://stackoverflow.com/a/23811427/1058763

でもなんで """ なの?

しかし、なんでRubyでわざわざ"""なんて書く人が出てくるんでしょうか?
僕は"""を自分で書いたことがないので、これは僕の推測になりますが、PythonやCoffeeScriptなど、他の言語の文字列リテラル(改行が入れられる文字列リテラル)をうっかりRubyでも書いてしまった、という理由があるのかもしれません。

Pythonの場合

print("""\
Usage: thingy [OPTIONS]
     -h                        Display this usage message
     -H hostname               Hostname to connect to
""")

出典 https://docs.python.org/ja/3/tutorial/introduction.html#strings

CofeeScriptの場合

html = """
       <strong>
         cup of coffeescript
       </strong>
       """

出典 https://coffeescript.org/#strings

しかし、Rubyであればこういうケースは"""ではなく、ヒアドキュメント構文を使うのが良いと思います。

msg = <<TEXT
Hello, world.
I am Ruby.
TEXT

puts msg
#=> Hello, world.
#   I am Ruby.

まとめ

というわけで、この記事では摩訶不思議なRubyの3連引用符(ダブルクオート3つ、またはシングルクオート3つ)の謎について説明しました。

日常的に使うことはまったくオススメしませんが、ちょっとしたトリビアとして「へえ〜」と思ってもらえれば幸いです?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VBScriptをつくってみる(制御構文編)

前回まででVBScriptの変数宣言機能・変数代入機能・演算機能を作りました。
今回は制御構文(If, For)を作ってみます。

まずは、制御構文に対応するように構文解析器を改良します。
VBScriptの文法を知っていればそんなに難しくない構文解析ルールですね。
一部TODOがありますが、そこは見なかったことにして下さい。

  block_statement : var_declartion
                  | if_statement
                  | loop_statement
                  | for_statement
                  | select_statement
                  | inline_statement nl

  # If Statement
  if_statement : 'If' expr 'Then' nl block_statement_list else_statement_list 'End' 'If' nl { result = AST::IfStatement.new(val[1], val[4], val[5]) }
               | 'If' expr 'Then' inline_statement else_opt end_if_opt nl                   { result = AST::IfStatement.new(val[1], val[3], val[4]) }
  else_statement_list : 'Elseif' expr 'Then' nl block_statement_list else_statement_list    { result = AST::IfStatement.new(val[1], val[4], val[5]) }
                      | 'Elseif' expr 'Then' inline_statement nl else_statement_list        { result = AST::IfStatement.new(val[1], val[3], val[5]) }
                      | 'Else' nl block_statement_list                                      { result = AST::ElseStatementList.new(val[2]) }
                      | 'Else' inline_statement nl                                          { result = AST::ElseStatementList.new(val[1]) }
                      |                                                                     { result = AST::Nop.new }
  else_opt : 'Else' inline_statement                                                        { result = val[1] }
           |                                                                                { result = AST::Nop.new }
  end_if_opt : 'End' 'If'
             |

  loop_statement : 'Do' 'While' expr nl block_statement_list 'Loop' nl { result = AST::WhileStatement.new(val[2], val[4]) }
                 | 'Do' 'Until' expr nl block_statement_list 'Loop' nl { result = AST::UntilStatement.new(val[2], val[4]) }
                 | 'Do' nl block_statement_list 'Loop' 'While' expr nl { result = AST::DoWhileStatement.new(val[5], val[2]) }
                 | 'Do' nl block_statement_list 'Loop' 'Until' expr nl { result = AST::DoUntilStatement.new(val[5], val[2]) }
                 | 'Do' nl block_statement_list 'Loop' nl { result = AST::Nop.new } # TODO: Exit文が実装されたらLoop文も実装する
                 | 'While' expr nl block_statement_list 'Wend' nl { result = AST::WhileStatement.new(val[1], val[3]) }

  for_statement : 'For' extended_id '=' expr 'To' expr step_opt nl block_statement_list 'Next' nl { result = AST::ForStatement.new(val[1], val[3], val[5], val[6], val[>
                | 'For' 'Each' extended_id 'In' expr nl block_statement_list 'Next' nl { result = AST::Nop.new } # TODO: 配列が実装されたらForEach文を実装する
  step_opt : 'Step' expr { result = val[1] }
           |             { result = AST::NumberLiteral.new(1) }

  # Select Statement
  select_statement : 'Select' 'Case' expr nl case_statement_list 'End' 'Select' nl  { result = AST::SelectStatement.new(val[2], val[4]) }
  case_statement_list : 'Case' expr nl_opt block_statement_list case_statement_list { result = val[4].unshift(AST::CaseStatement.new(val[1], val[3])) }
                      | 'Case' 'Else' nl_opt block_statement_list                   { result = [AST::CaseElseStatement.new(val[3])] }
                      |                                                             { result = [] }

次に各制御構文の実装を紹介します。
まずは、If文です。
条件式をevalし、その結果がTrueかFalseかに応じてThen節もしくはElse節の実行(eval)を行います。

  class IfStatement < List
    def initialize(expr, then_statement_list, else_statement_list)
      super([expr, then_statement_list, else_statement_list])
    end

    def eval(environment)
      if child(0).eval(environment)
        child(1).eval(environment)
      else
        child(2).eval(environment)
      end
    end
  end

While文は特に説明をしなくても自明かと思います。

  class WhileStatement < List
    def initialize(expr, block_statement_list)
      @expr = expr
      super([block_statement_list])
    end

    def eval(environment)
      while @expr.eval(environment) do
        child(0).eval(environment)
      end
    end
  end

最後にFor文の紹介です。
For文は引数が多いために実装がやや面倒くさいです。
To句やStep句には即値だけでなく式も書けることの考慮が必要です。

  class ForStatement < List
    def initialize(id, from, to, step, block_statement_list)
      @id = id
      @from = from
      @to = to
      @step = step
      super([block_statement_list])
    end

    def eval(environment)
      environment[@id.identifier] = @from.eval(environment)
      to = @to.eval(environment)
      step = @step.eval(environment)
      if step >= 0
        while environment[@id.identifier] <= to
          child(0).eval(environment)
          environment[@id.identifier] += step
        end
      else
        while environment[@id.identifier] >= to
          child(0).eval(environment)
          environment[@id.identifier] += step
        end
      end
    end
  end

ここまでの機能を使うことで、フィボナッチ数のN項目を計算できるようになりました。

' フィボナッチ数のn項目を計算する

Dim n, tmp1, tmp2, answer

n = 10

tmp1 = 1
tmp2 = 1

If n = 1 Or n = 2 Then
  answer = 1
Else
  For i = 1 To n - 2 Step 1
    answer = tmp1 + tmp2
    tmp1 = tmp2
    tmp2 = answer
  Next
End If

' この時点でanswerに答えがセットされている

怒涛のVBScript記事連投は、VBScriptっぽい文法を持ったチューリング完全な言語が完成したあたりで一旦中断します。
気が向いたら、また来年この続きから言語実装を始めるかもしれません。

明日のZOZOテクノロジーズアドベントカレンダー#3 @zt_takumi_ito さんです。
是非そちらもご覧ください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsの現場でよく見る書き方【実例あり】

はじめに

こんにちは、@ryokky59と申します!
未経験からエンジニアになって1年経ちました!
今回はRailsを実務で1年間触ってきて、この書き方は最初に知っておけばスムーズにソースコードが読めたなぁと感じたことをまとめてみました!

この記事で実務と独学の壁がすこしでも埋められるお手伝いができたらいいなと思います!

対象読者

  • 未経験からエンジニアを目指してRailsを勉強している方
  • 普段は他言語を使っているけど今度はRailsを使う現場に行かれる方
  • 自社以外のRailsの書き方を知ってみたい方

Railsのいろんな書き方

埋め込みRuby

文字列の中にRubyの式や変数を埋め込める

user_name = "taro"
"こんにちは#{user_name}さん。今日は#{Date.today}です"
# => "こんにちはtaroさん。今日は2019-12-14です"

""(ダブルクォーテーション)じゃないとRubyが展開されないので注意です。

||=

変数に値を入れるときに、変数がnilかfalseのときのみ値を入れることができます

user1 = "taro"
user1 ||= "jiro"
user1 
# => "taro" ("jiro"が代入されていない)

user2 = nil
user2 ||= "jiro"
user2
# => "jiro" ("jiro"が代入されている)

後置if

例えば以下のようなfalse側がいらないif文がある時

if true
  "trueでhoge"
end
# => "trueでhoge"

こういう時は以下のように一行で書くことができます

"trueでhoge" if true # => "trueでhoge"

例を挙げるとメソッドから抜ける時によく使われます(ガード節といいます。詳しくはコチラ)

def puts_user_name(user_id)
  user = User.find_by(id: user_id)
  return if user.nil? # もしuserがnilだったらreturnでメソッドから抜ける

  puts user.name
end

三項演算子

例えば以下のような普通のif文があるとき、

if true
  puts("trueでhoge")
else
  puts("falseでhuga")
end
# trueでhoge

このような if~else~end で終わるようなif文であれば一行で以下のように書くことができます

true ? puts("trueでhoge") : puts("falseでhuga") # trueでhoge

メソッドの引数その1

メソッドの引数の書き方は ()でも(半角スペース)でも大丈夫です。

User.find(1) # => {id: 1, name: "idが1のユーザー"} 

User.find 1  # => {id: 1, name: "idが1のユーザー"} 

使い分けとしては例えば、上の三項演算子の例ではputs "trueでhoge"みたいに()ではなく、(半角スペース)で書くとSyntaxError unexpected ':' がでてしまいます。
三項演算子は(半角スペース)で次にどの文字がきて欲しいかを予測します。
なのでputs "trueでhoge" と書いてしまうとputsの次が(半角スペース)で:が来ることを期待しているのに"trueでhoge" が来ているのでエラーになります。

このように、()で囲って一つの文として評価されたいか、(半角スペース)で繋げて読みやすくするかを使い分けることができます。

メソッドの引数その2

メソッドに渡す引数の種類もいくつか種類があります。

デフォルト引数

引数にデフォルト値を入れておいて、その引数がなければデフォルト値を使います

def puts_string(string="文字列")
  puts string
end

puts_string # "文字列"

キーワード引数

引数をハッシュで指定する。
実行するときにキーワードも指定する必要がある。
引数が複数だったり、明示的にしたいときに使われる。

def puts_string(string:)
  puts string
end

puts_string(string: "文字列") # "文字列"

可変長引数

引数を配列として受け取ることができる

def name_array(*name)
  p name
end

puts_name_array("taro", "jiro", "saburo")
# => ["taro", "jiro", "saburo"]

!◯◯(変数)

変数の前に!を書くと中身を反転したbooleanの形(trueまたはfalse)に変換することができます

下記のようにstringという変数の前に一つ!をつけると反転してfalse、さらにもう一つ!をつけるとさらに反転してtrueを返します。

string = "文字列"
string   # => "文字列"
!string  # => false
!!string # => true

変数の中身をnilにすると理解しやすいかもしれません

nil_val = nil
nil_val   # => nil
!nil_val  # => true
!!nil_val # => false

%記法

配列はよく%記法を用いて書かれます。

%w(hoge huga foo bar) # => ["hoge", "huga", "foo", "bar"]

注意するところは配列の中身は必ずstringになるというところです

%w(1 2 3) # => ["1", "2", "3"]

シンボルにすることもできます

%i(new create delete) # => [:new, :create, :delete]

{}

ブロックは do~end で書くことが多いかもしれませんが {} を使って一行で書く時に読みやすくすることができます。

%w(hoge huga foo).each do |string|
  puts string
end
%w(hoge huga foo).each{ |string| puts string }

この二つはどちらも同じ結果を返します。
do{ に、 end} に置き換わっただけですね
ブロックの中身が短く単純な時に便利です。

[1, 2, 3, 4].select { |item| item % 2 == 0 } # => [2, 4]

上のように selectany? など配列、ハッシュを扱うメソッドを使う時によく見られます

map(&:〇〇)

こちらは先ほどの{}で囲むブロック分よりさらに短縮したような書き方です。

users.map { |user| user.name } # => ["taro", "jiro", "saburo"]

users.map(&:name) # => ["taro", "jiro", "saburo"]

上の例ではオブジェクトのキーを指定して値を取り出しています
これはブロックをprocに変換しているからできます。
実際はmapメソッドだけでなく他の配列、ハッシュを扱うメソッドで使うことができます。

procとかは聞き慣れないかなと思うので参考記事を置いておきます
Rubyのmap &:to_iとはなんなのか
Ruby Procについて学ぶ

items.sort_by(&:created_at) # itemのcreated_atが古い順に並び替える

includes

引数に書いたモデルを先読みしてキャッシュしておく。
簡単にいうと不要なSQLを発行しないようにしてパフォーマンス低下を防ぐ(N+1問題を防ぐ)

@user = User.all.includes(:items)

↑のようにしておくと@user.itemsをeachで回したときにitem.nameとかでitemの数だけSQLが発行されるのを防ぐ
詳しくは下記記事を参考にどうぞ
Rails で includes して N+1 問題対策

&.(セーフナビゲーション)

別名ぼっち演算子
エラーの代わりにnilを返してくれます。

# userのnameがnilのとき

user.name.email # => NoMethodError: undefined method `length' for nil:NilClass

user.name&.email # => nil

nilの値であるレシーバ(name)に対してlengthメソッドを実行するとエラーが出ますが、&.でチェーンすることでnilに変わります。
nilが入る可能性のあるカラムを操作するときによく使われます。

each_with_index

eachで回すときにindexが欲しいときに使います。

users = %w(taro jiro saburo)

users.each_with_index do |user, index|
  puts "user: #{user}, index: #{index}"
end

# user: taro, index: 0
# user: jiro, index: 1
# user: saburo, index: 2

indexを任意の数字で始めることもできます

users = %w(taro jiro saburo)

# eachにチェーンしてwith_indexを繋げていることに注意
users.each.with_index(1) do |user, index|
  puts "user: #{user}, index: #{index}"
end

# user: taro, index: 1
# user: jiro, index: 2
# user: saburo, index: 3

おわりに

現場では他にも色々書き方はあると思いますが、ここまでの内容を抑えておけばそこそこスムーズにソースコードを読めるのではないでしょうか?
他にも「うちではこんな書き方あるよ!」みたいな方はお気軽にコメントください!

他記事への参考リンク

これからRailsを触っていく方に役に立つなと感じたリンクです。
(一部この記事を書く際にも参考にさせていただきました。m(_ _)m)
他言語経験者がRailsの案件にジョインしたときに、何を足掛かりにすべきか
差をつけるRuby

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】オブジェクト指向について

はじめに

Rubyに最初に触れる方はオブジェクト指向という言葉はよく聞くけれどいまいち何のことを言っているかわからないという人も多いと思います。なので、このオブジェクト指向について自分の思考の整理も兼ねて記事にしました。
ちなみにオブジェクト指向はRuby以外にもjava,phpなどにも用いられる概念です。

オブジェクト指向とは

簡単にいうとオブジェクト指向とはオブジェクトを中心に回り、複数のオブジェクトを組み合わせることでプログラムを構築していくという考え方のことです。

オブジェクトとは

オブジェクトとはデータや処理の集まりのことを指します。
ただし闇雲なデータや処理が集まったものではなく、一つのテーマの下で集まったものです。
イメージとしては、オブジェクトというのは概念のようなものではクラスとインスタンスによってできているという感じです。
またオブジェクトは振る舞いというものを持っています。振る舞いとは自分自身に対する操作のことで、
仮にAさんとBさんという別々のオブジェクトがいた場合に、名前を教えてと言うと、Aさんは「私はAです」、Bさんは「私はBです」というふうに別々の振る舞いを持っています。

クラスとは

オブジェクト指向には大切な概念としてクラスというものが存在します。
簡単にいうとオブジェクトの設計図のことで、一つのテーマに沿った振る舞いや情報の保持などをひとまとまりにしたものです。
今回はCarというクラスを作ってみます。

class Car
  def initialize(car_name, mileage, color)
    @car_name = car_name
    @mileage = mileage
    @color = color
  end

  def answer_car_name
    puts @car_name
  end
end


//実行結果
Prius = Car.new("prius", "50000", "blue")
=> #<Car:0x007fc55c1604f8 @car_name="prius", @mileage="50000", @color="blue">
irb(main):016:0> Car.answer_car_name
prius

Carというクラスにはanswer_car_nameというCar自身の振る舞いが含まれています。
このようにクラスを設定するとこの一つの操作(名前を出力する)についてはこのオブジェクトに任せることができます。

開発者にとってのオブジェクト指向

オブジェクト指向はユーザーにとって何かしらの利点があるわけではありません。あくまでも開発者が円滑にコミュニケーションをとるために考えられた概念です。

守るべき原則

DRY

"Don't Repeat Yourself"の略で、繰り返しを避けるという意味です。
これはコードの量が増えることを避けて、できるだけバグの原因をなくそうという考え方です。
また、コードを書き直す際に、重複した表現を書いていた場合、どちらも直さないといけなくなり、無駄な労力を割いてしまいます。

YAGNI

"You ain't gonna need it"の略で、必要になったときに必要な機能だけ実装すると言う原則です。
必要になるかもしれない、と言う理由で使わないコードを書いてしまった場合、後々それがバグの原因になることがあります。

単一責任の原則

クラスが持つ役割は一つだけにすると言う原則です。もしもCalendarクラスがあるとします。Calenderクラスには予定を操作する機能と、予定をグラフで可視化する機能があるとします、しかしその二つの機能を一つのクラスが持つことは望ましくありません。なぜなら、予定をグラフで可視化する機能を編集したいときに、カレンダーを操作する機能を司る部分も編集する必要が出てくるかもしれないからです。なのでしっかりと二つにクラスをわけ、変更したい機能のみを編集するためにクラスを分ます。

インターフェイス分離の原則

複数のクライアントが使用する機能の場合、どのクライアントにも対応したような網羅的な機能の場合、一クライアントが使っているときに使わないメソッドなどが出てきてしまいます。そのような場合は望ましくなく、適切な処理ができない場合があります。なので、一クライアント単位で使う機能をグループ分けしあまり網羅的な機能を作らないように心がけることが大切です。

まとめ

いかがだったでしょうか。このオブジェクト指向はとてもわかりづらいと感じてしまったかもしれません。しかし、実際に自分の手でコードを書いて再びこの考え方を復習したときに理解度がより深まるのではないかと思います。とにかくコードを書き、オブジェクト指向の全体の流れのようなものを掴むことをお勧めします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ポートフォリオ】UROGを開発しました

どんなアプリを作ったか - 概要

カテゴリー毎にURLを分けて保存できるメモアプリです。
https://www.urog.me/
00bcn-u0lm8.gif

お試しで利用したい方は、以下のemail,passwordを使ってみてください。
(もちろん新しくユーザー登録も可能です)

email:    aaaaa@email.com
password: aaaaaaaa

github:https://github.com/koao123/urog_app

アプリ・筆者についての解説

使用言語・フレームワーク:Ruby・Rails・HTML・CSS・BOOTSTRAP
使用ツール: github, heroku, AWS(cloud9)

開発期間:2ヶ月
勉強期間:半年

このアプリでできること

ユーザー登録
スクリーンショット 2019-12-11 23.45.46.png

ログイン(ログアウト)
スクリーンショット 2019-12-11 23.45.53.png

カテゴリー毎に分けて投稿が可能
00bcn-u0lm8.gif

高い一覧性で投稿を見ることが可能です。

なぜこのアプリを作ったか

私は将来役に立つだろう記事(Web)や、気づきを与えられる記事に遭遇すると、将来のために保存するようにしています。しかし実際問題、必要な時にその記事を保管場所から取り出せることがほとんどありません。

この問題が改善され、緊急性が上がり必要になった情報(Webページ)を必要な時に入手できるようになれば、
将来の無駄な時間が削減され自分の成長効率が上がると考えました。

具体的に何を解決しようとした結果、このアプリを作ったのか

保存したWeb記事を閲覧する際のハードルとして、「一覧性の低さ」が挙げられる。これを解決するためにこのアプリを作りました。

ーーー

現状では、記事をどのサイトのどの場所に置いたか分からず、辿り着くまでにかなりの手間がかかるようになっています。

スクリーンショット 2019-12-14 1.06.09.png
(記事保存先検索候補が多く、TOPページで全体把握できないため、更にそこから探す必要があります)

そして、理想の状態は
「探すべき場所が一箇所で、その場所にたどり着いた後、保存した記事をすぐに見つけれる」ことです。この状態だと、素早く記事を探せるので、実用性があると考えました。
スクリーンショット 2019-12-14 1.17.00.png

理想から考える現状のギャップとしては
「保存場所に行った後、保存した記事の"一覧性が低い"こと(メモアプリなので、二階層。全て初めのページで見れない)」と「"カテゴリー分け等がされていない"ので、保存ページに行った後、どの範囲を探せば良いか分からない」ということです。

そのため「一ページで全体が把握できるほど一覧性が高く」「カテゴリー分けができる」メモアプリを作ろうと考え、このアプリを作りました。

どうやって作ったのか

以下の手順で行いました。

サービス企画
理想と現状のギャップを考え、最低限どのような機能を持ったアプリを作ればその課題は解決できそうか?を考えた

必要な機能の洗い出し
DB設計(postテーブル・userテーブル等)
UI設計

バックエンドをRailsで実装

フロントエンドを実装(HTML、CSS)

その過程で工夫した点

サービス設計

ユーザーにとってどんな機能があれば使うか?何があれば課題を解決出来そうか?を自分の行動を分解し突き詰めて考えました。(対象ユーザーは自分なので)

カテゴリー機能

ただURLが投稿できるだけではなく、カテゴリー毎に投稿できるようにした。これにより、URLがカテゴリー毎に探しやすくなる。

LPに書く文章の工夫

このアプリの価値とターゲットユーザーを考え、ターゲットユーザーが「このサービスいいな」と思うような文章を考えた。

苦しんだ点(その解決過程)と学び

正体不明のエラーの対応

特に苦しんだ点は、エラーを読んでも分からず、エラーメッセージを検索してもあまり分からなかったエラーです。こういう時はエラーメッセージが示していることや、検索語のページで特定の方が言っていることが理解できていないことが多かったです(自分は相手が見えている構造を理解できていないため)。なので今後は、行き詰まった時は見える情報から構造を確認し、エラーの対処に当たろうと思います。

サービス設計と実際の開発との解離

サービス設計ののち、DB設計とUI設計をきっちり行ったが、実際に開発すると、認識できていなかった技術的に難しいなどの問題が多数出てきて設計をやり直すことになりました。当たり前ですが、自分で実装できることの重要さを感じました。

このアプリの課題

新たなる問題

このアプリの目的は、「緊急性が低いURLを保存しておき、緊急性が高くなった時に取り出せるようにすること」です。この問題は、このアプリを作ったことによりある程度解決されたと思っているが、私は「保存時にWebアプリを開くのが面倒」と感じてしまっています。
そのため、この問題を解決する必要があると考えています(LINE APIを利用すれば可能・・・?)

保守性の問題

今回はテストを書いておらず、全く後の改善時のことを考えていないので、取り組もうと思います。

フロントの実装

フロントは見よう見まねで実装したため、正直なところちゃんとしたcssも書けておらず、レスポンシブ対応さえできていません。Ajax対応もさせる必要があるので、早く勉強して取り組もうと思っています。

さいごに

実際に動くアプリを完成させれたのは自信になったが、自分の技術レベルの低さを目の当たりにすることになりました。これからも勉強して行きたいと思います・・・。

※これは、就職活動をする上で見ていただく採用担当の方向けに書いた記事です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】文字列の文字数を求めるアルゴリズム

この記事ではRuby 2.6を使っています。

countlengthsizeを使用せずに文字列の文字数を取得するというやつです。
forsplitの使い方の練習も兼ねてやってみました。

要件

  • count("文字列")のように、関数に対して文字列の引数を渡すと、文字列の文字数が返却される
  • size, length, countメソッドを使用してはならない

例えば"hello!"という文字列を与えたときに6が返ってきたら成功です。

手順のフロー化

手順をフロー化します。

  1. 文字列を取得する。
  2. 文字列を配列として1文字ずつ分割
  3. 配列を繰り返し処理、その都度変数に+1を実行

実装

.split("")のように引数に空文字列""を指定すると、1文字ごとに分割した配列を返します。
ここでは文字列 "hello!" が配列["h", "e", "l", "l", "o", "!"]となります。

count.rb
def array(str)
  array = str.split("")
end

配列に対してforを使って繰り返し処理をしていきます。
繰り返し処理のたびに変数countに+1をしていきます。

count.rb
def count(str)
  count = 0
  for chara in array(str) do
    count += 1
  end
end
count.rb
p count("Hello!")
#6が返ってきた

6が返って来るので成功です!
今回はforを使用していますが、whileeachでも同様にできます。

ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dry-monadsを使ってRubyでモナドの夢を見る

はじめに

皆さんはRuby、好きですか?僕は好きです。
皆さんはモナド、好きですか?僕は好きです。

好きなものと好きなもの、どっちも使いたくなるのが人間の性。
どうにかしてRubyでモナドを使いたい!

ということで、Rubyでモナドを使って、Rubyでよく書くありがちなコードをいい感じにしていきます。

モナドってなに?

モナドはHaskellなどの関数型言語で使われる概念です。
細かい定義は他の記事に任せますが、簡単にいってしまうと「書いたコード(文字通り)よりも外の世界から受ける影響に安全にアクセスする方法」です。こういうのをプログラミングでは「副作用」って言ったりします。
モナドについてはここここの記事が個人的に勉強になりました。

モナドが力を発揮する場面としてよくあげられるのが「IO」です。IOはまさに「自分が書いたコードの外から受ける影響」ですよね。
入力ではコードを実行するまでどんな値が入ってくるかわかりません。冷静に考えるとこれは結構怖いことだったりします。書いたコードは自分でいくらでもバグらないように修正できますが、外部からの影響には「備える」ことしかできません。
そんな時にモナドを使うと、「外界の影響」を箱に詰めてブラックボックス化しながら、コードとしてはやりたいことを素直に記述するだけでうまいコードを書くことができるようになります。

ここまでの説明を読むだけでも、モナドはなんだかいいもののように感じませんか?(良いものです)
残念ながらRubyには組み込みの文法としてのモナドは存在しません。(今後も多分入らない)
しかし、型安全やバリデーションをシステムに提供してくれるライブラリ群、dry-rbdry-monadsというgemがあります!
このgemを使ってモナドの力をRubyに加えてみます。

実践

dry-monadsは関数型言語のモナドをRubyでも扱えるようにしたgemですが、正確にいうと関数型言語におけるモナドとは異なります。
詳しいことは省きますが、結論から言うとモナド則を満たさない記述ができてしまうからです(動的型付け言語だから仕方がないのかもしれない)
しかし、それでもモナドが提供してくれる恩恵を得ることはできます。

言葉で書いてよくもわからないと思うので、早速手続き型プログラミングで書いた場合とモナドを使用して書いた場合のコードを載せます。

手続き型言語ではIO処理ではよくガード条件を使って外部からの影響に備えますね。
例えばRailsのActiveRecordとかを使っているとよくこんな感じのコードを書くんじゃないでしょうか?(ちょっと大げさに書いてます)

user = User.find_by(id: user_id)
address = Address.find_by(id: address_id)

if user.present? && address.present?
  if user.update(address: address)
    ...
  else
    ...
  end
else
  ...
end

このように、手続き型で素朴に記述するとUser.find_by が存在しているかどうか、取得したUserと関連をもつArticleが存在するかどうかを想定したコードになります。

これにdry-rbを適用して書くとこうなります。

require 'dry/monads'

extend Dry::Monads[:maybe]

result = Maybe(User.find_by(id: user_id)).bind do |user|
  Maybe(Address.find_by(id: address_id)).fmap do |address|
    user.update(address: address)
  end
end.to_result

if result.success?
...
else
...
end

このようにモナドを使うことで、userがなんの値になろうがarticleがなんの値になろうが途中の処理は全てモナドが隠してくれます。これによってコードを書く上では最終結果に対してsuccess?trueになるかどうかだけを考えれば良くなります。(to_resultメソッドを用いてResultモナドに変換しています)思考がスッキリするのを感じますね。
また、ruby2.7で使うことができるようになるパターンマッチを用いると、↓のようにResultへのキャストをする必要もなく記述できるようになります

require 'dry/monads'

extend Dry::Monads[:maybe]

result = Maybe(User.find_by(id: user_id)).bind do |user|
  Maybe(Address.find_by(id: address_id)).fmap do |address|
    user.update(address: address)
  end
end

case result
in Some(_)
...
in None()
...
end

この例ではオプショナル型に近いようなMaybeモナドを使いましたが、そのほかにもモナドはたくさんあるのでそれぞれの使い方を紹介します

Maybe

例でも用いたMaybeは、nilを受け取った際にはNone()を返し、それ以外の時はSome(value)を返します。
これを用いることで最終結果がSome or Noneになるため、最後にその検証だけをすればよくなります。
https://dry-rb.org/gems/dry-monads/1.0/maybe/
コード再掲

require 'dry/monads'

extend Dry::Monads[:maybe]

result = Maybe(User.find_by(id: user_id)).bind do |user|
  Maybe(Address.find_by(id: address_id)).fmap do |address|
    user.update(address: address)
  end
end

case result
in Some(_)
...
in None()
...
end

Result

ResultはMaybeに似たようなモナドです。MaybeがnilNoneに変換してくれたのに対し、Resultは自分でSuccessFailureの定義をする必要があります。
https://dry-rb.org/gems/dry-monads/1.0/result/

require 'dry/monads'

extend Dry::Monads[:result]

result = find_user(user_id).bind do |user|
  find_address(address).fmap do |address|
    user.update(address: address)
  end
end

case result
in Success(_)
...
in Failure()
...
end


def find_user(user_id)
  user = User.find_by(id: user_id)
  user.present? ? Success(user) : Failure()
end

def find_address(address_id)
  address = Address.find_by(id: address_id)
  address.present? ? Success(address) : Failure()
end

Try

Tryはエラーハンドリングに使うことができるモナドです。
これを用いることで、エラー処理をcall内に閉じ込めることができます。
https://dry-rb.org/gems/dry-monads/1.0/try/

require 'dry/monads'

class UpdateUser
  include Dry::Monads[:try]

  attr_reader :user_id, :address_id

  def initialize(user_id, address_id)
    @user_id = user_id
    @address_id = address_id
  end

  def call
    Try { User.find(user_id) }.bind do |user|
      Try { Address.find(address_id) }.bind do |address|
        Try { user.update!(address: address) }
      end
    end
  end
end

result = UpdateUser.new(user_id, address_id).call

case result
  in Try::Value(_)
    ...
  in Try::Error(_)
    ...
end

List

Listは配列に対するイテレーションに使うことができるモナドです。
マップ処理などをモナドの世界に閉じ込めることができます。
https://dry-rb.org/gems/dry-monads/1.0/list/

require 'dry/monads/list'

M = Dry::Monads

M::List[1, 2].bind { |x| [x - 1] } # => List[0, 1]
M::List[1, 2].bind(-> (x) { [x, x * 2] }) # => List[1, 2, 2, 4]

M::List[1, nil].bind { |x| [x - 1] } # => raise Error

Task

Taskは非同期処理に対して使うことができるモナドです。
これを用いることで、JSのasyncawaitのような処理を行うことができるようになります。
https://dry-rb.org/gems/dry-monads/1.0/task/

require 'dry/monads'

class AsyncTask
  include Dry::Monads[:task]

  def call
    task1 = Task { fetch_task1 }
    task2 = Task { fetch_task2 }

    task1.bind { |t1| task2.fmap { |t2| [t1, t2] } }
  end

  def fetch_task1
    sleep 3
    'task1'
  end

  def fetch_task2
    sleep 2
    'task2'
  end
end

async_task = AsyncTask.new

task = async_task.call

task.fmap do |t1, t2|
  puts "Task: #{t1}"
  puts "Task: #{t2}"
end

sleep 10
puts 'done'

まとめ

状況に応じてこれらのモナドを使うことで、やりたいことを素直に記述したコードがかけるようになります。
少しでもご興味を持っていただけたら幸いです。

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby on Rails5 アプリケーション プログラミング]学習ログ#3[2019/12/13]

前回 -> [Ruby on Rails5 アプリケーション プログラミング]学習ログ#2[2019/12/12]
次回 -> [[Ruby on Rails5 アプリケーション プログラミング]学習ログ#4[2019/12/14]]


目的

Progateで学んだことをローカル環境で手を動かして復習することで、基礎を固める

使用する教材

山田祥寛(2017). RubyonRails5 アプリケーションプログラミング 株式会社技術評論社

Q.「RubyonRails5 アプリケーションプログラミング」とは?

A.ProgateのRoRコースを終えた後にするRoRの勉強としておすすめされている本

出典
(Samurai Blog / Ruby on Rails学習本おすすめ6選【入門者〜上級者までレベル別に解説】)[https://www.sejuku.net/blog/110292]


今日したこと

ビュー開発の基礎(p104~p136)

(復習)コントローラ/ビュー/ルート定義 を作成

console
#viewという名前のコントローラクラスを作成
rails g controller view
#view以外の名前のコントローラクラスhogeを間違って作成した場合の切り戻し
rails destroy controller hoge

#ビューの作成
[view_controller] , [books_controller]
を編集

#ルート定義
[routes.rb]
get 'view/keyword'
post 'keyword/search'

フォーム関連のビューヘルパーの作成

console
#viewディレクトリに移動
cd app/views/view
#テンプレートファイルの作成
vi hogehoge.html.erb

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む