Rails でアプリを作る② RSpec 導入

テストを書いていきます。今回はRSpecを使用します。以下を Gemfile に追加してください。

[Gemfile]

group :development, :test do
  # 省略
  gem "rspec-rails"
end
% bundle

RSpec を設定する

RSpecをインストールします。

% rails g rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

RSpecの出力をドキュメント形式に変更するために、/rspecを編集します。

[.rspec]

--require spec_helper
--format documentation

Railsには、railsコマンド、Rakeコマンド、テストを高速に実行するためのプリローダーであるSpringがデフォルトで付属しています。

Spring is a Rails application preloader. It speeds up development by keeping your application running in the background, so you don't need to boot it every time you run a test, rake task or migration.

binstub を使用すると、Spring を Rspec で動作させることができます。binstub を使うには、 gem spring-commands-rspecGemfiledevelopment グループに配置する必要があります。Springもコメントアウトされていたのでコメントインしました。

[Gemfile]

group :development do

  # 省略
  gem "spring"

  gem "spring-commands-rspec"
end
% bundle

binstub を作成します。

% bundle exec spring binstub rspec
* bin/rspec: generated with Spring

アプリケーションの bin ディレクトリに rspec という実行ファイルが作成されます。

動作確認をします。

% bin/rspec          
xxxx/3.0.3/lib/ruby/gems/3.0.0/gems/spring-4.0.0/lib/spring/application.rb:101:in `block in preload': Spring reloads, and therefore needs the application to have reloading enabled.
Please, set config.cache_classes to false in config/environments/test.rb.

エラーが出ました。Springはアプリケーションでリロードを有効にする必要があるようです。

https://github.com/rails/spring

Please, make sure config.cache_classes is false in the environments that Spring manages. That setting is typically configured in config/environments/*.rb. In particular, make sure it is false for the test environment.

[config/environments/test.rb]

Rails.application.configure do
  # 省略
  config.cache_classes = false
  # 省略

もう一度動作確認をします。

% bin/rspec
DEBUGGER[spring app    | kioku | started 7 mins ago | test mode#97048]: Attaching after process 96821 fork to child process 97048
Running via Spring preloader in process 97048
No examples found.

Finished in 0.00044 seconds (files took 0.16712 seconds to load)
0 examples, 0 failures

問題なく動作しました。

最後に、rails g コマンドで同時生成されるファイルを変更します。

[config/application.rb]

# 省略
module Kioku
  class Application < Rails::Application
    # 省略
    config.generators do |g|
      g.test_framework :rspec,
                       fixtures: false,
                       view_specs: false,
                       helper_specs: false,
                       routing_spec: false
    end
  end
end

続きは次回。

Rails でアプリを作る①

Railsを使う機会があったので、復習のためにアプリを作ろうと思います。

ちょうど資格の勉強をしているので、学習に関連するアプリが良いかなと。エビングハウス忘却曲線に基づき、復習するタイミングを管理する学習記録アプリです。

アプリの作成

RubyMineでアプリの雛形を作成します。アプリ名は「kioku」としました。

データベースの変更

データベースをPostgreSQLに変更します。rails6からは rails db:system:change が使えるようになりました。

% rails db:system:change --to=postgresql 

を実行します。

このコマンドを実行すると、Gemfileconfig/database.yml が変更されます。Gemfile が変更されたので、bundle コマンドを実行します。

% handle

デフォルトで作成された db/development.sqlite3 は必要ないので削除します。

rails db:create コマンドでデータベースを作成します。

% rails db:create
Created database 'kioku_development'
Created database 'kioku_test'

モデルの検討

モデルについて考えます。アプリの登場人物について考えます。

  • 学習記録に関する情報
  • 復習に関する情報

まず、「学習記録に関する情報」の属性について考えてみます。

  • タイトル
  • 内容

学習記録の情報モデルを作成します。モデルは rails g model [モデル名] [カラム名:型] というコマンドで作成します。

% rails g model Task name:string content:string

マイグレーションファイルは、以下のように作成されます。

[db/migrate/20220312000839_create_tasks.rb].

class CreateTasks < ActiveRecord::Migration[7.0]
  def change
    create_table :tasks do |t|
      t.string :name, null: false, default: ""
      t.string :content, null: false, default: ""

      t.timestamps
    end
  end
end

rails db:migrate を実行してDBに反映させます。

% rails db:migrate

続きは次回。

LaravelとNUXTを使ったプロジェクトの準備

Laravel側

プロジェクトの作成

laravel new ${プロジェクト名}

laravel new synapse_laravel

バージョンを指定する場合

$ composer create-project ”laravel/laravel=6.*” synapse_laravel

サーバーを起動

cd synapse_laravel
php artisan serve

API作成

routes/api.phpファイルを編集

Route::get('/', function() {
    return 'helloworld';
});

http://localhost:8000/apiに接続するとhelloworldと表示

NUXT側

プロジェクトの作成

npm create-nuxt-app ${プロジェクト名}

npx create-nuxt-app synapse_nuxt

@nuxtjs/axiosの導入

cd synapse_nuxt
npm install --save @nuxtjs/axios

nuxt.config.jsファイルを編集してmodules"@nuxtjs/axios"を追記する

modules: [
  "@nuxtjs/axios"
],

サーバーを起動

yarn run dev

http://localhost:3000/に接続するとロゴの下にsynapse_nuxtと表示

API使用

page/index.vueファイルを編集

<template>
  <div class="container">
    <div>
      <Logo />
      <h1 class="title">
        {{ data }} //synapse_nuxtを編集
      </h1>
      // 省略
    </div>
  </div>
</template>

<script>
export default {
  async asyncData(app) { //ページコンポーネントがローディングされる前に呼びされる
    const data = await app.$axios.$get('http://localhost:8000/api')
    return {
      data
    }
  }
}
</script>

http://localhost:3000/に接続するとロゴの下にhelloworldと表示されていたら成功!!

リーダブルコード第3章「誤解されない名前」の輪読会をした備忘録と感想

毎週月曜日に「リーダブルコード」の輪読会を行っています。 今回は3章を終えた感想や備備忘録を書きます。 3章は、名前が「他の意味と間違えられることはないだろうか?」というテーマです。

誤解されない名前

英語がネイティブでない自分にとって、英語のニュアンスを掴むことは難しいです。 たとえばfilterという単語。 辞書をひくと

〈水・ガスなど〉をろ過する, こしてきれいにする; …をろ過して取り除く; 〈光・音など〉の一部を除去[カット]する, 遮断する; 〈不要・有害なもの〉を除去する; 〈人・物〉を取捨選択する

とありますが、この説明を見ると「除外する」という意味がありそうです。 しかし、phparray_filterTRUEのものを配列で返します。 つまり、「選択をする」という意味をもっています。

このように英語の単語は曖昧なニュアンスを含んでいるので、「選択をする」という意味で名前をつけるならばselect()とすることで、誤解のない名前になります。

lengthという単語も、どういう使われ方をしているかで名前を付けます。 最大の長さという意味で使うのであればmax_lengthとします。 さらにlengthという単語自体も曖昧で、「バイト数」なのか「文字数」なのか、はたまた「単語数」なのか? 文字数という意味で使うのであればmax_charとするのが良さそうです。

正直、ここまでは考えていなかったです・・。 いろんな解釈ができる名前は避け、明確な名前をつけましょう。

限界値と範囲と包含/排他的範囲

「以上」か「より大きい」か?「以下」か「未満」か? この違いで起こるエラーを「off_by_oneエラー」というようです。

off_by_oneエラーを避けるには「>=」と「>」に注意するというのもありますが、誤解されない名前をつけることでも避けることができます。 商品カートの上限値であればCART_TOO_BIG_LIMITという名前ではなく、限界値を含むMAX_ITEMS_IN_CARTとつけるべきです。

範囲を指定するときはfirstとlastを使うというのは馴染みが深いです。

print integer_range(start=2, stop=4)

「2, 3」ではなく「2, 3, 4」を表示したいのであれば、(start, stop)ではなく(first, last)を使うというものです。

包含/排他的範囲にはbeginとendを使う。

PrintEventsInRange(“OTC 16 12:00am”, “OTC 17 12:00am”)
PrintEventsInRange(“OTC 16 12:00am”, “OTC 16 11:59.9999pm”)

うーん。。こちらは馴染みがないですが、意識しておかないとついstartとかlastとかを使ってしまいそうですね。

ブール値の名前

ブール値の頭にisやhasをつけるのもすでにできていることで、とくに議論はなかったです。 ブール値では否定形は避けるというのも納得です。というか書いててわかりにくいですよね。

× bool disable_ssl = false;
○ bool use_ssl = true;

この項目は共感しやすかったです。

ユーザの期待に合わせる

getsizeは「軽量アクセサ」が期待されるというのは、今までまったく意識してこなかったです。 ふつうにDBから取得するときはgetくらいの意識しかなかったですね。 全件取得のときとかは気をつけないとですね。

まだまだ、馴染みがないことも多く、経験の無さを実感します。 とくにユーザが期待することとかは、周りの同僚も意識していなく、結局はチーム内で共通の認識があれば良いねという結論に達しました。 ただ、このような認識の違いがあるということはしっかり覚えておかないと、別のプロジェクトに行ったとき苦労しそうだなと思いました。

【JavaScript】canvasで画像上に座標を指定してブロック枠を描画する方法

  • Vue.jsを使用
  • 画像を描画
  • 画像上に座標を指定してブロック枠を描画

コード

<template>
  <div>
    <!-- canvas要素を配置、サイズ、refを指定 -->
    <canvas ref="canvas" :width="canvasWidth" :height="canvasHeight" />
  </div>
</template>
<script>
export default {
  data() {
    return {
      canvasWidth: 400,
      canvasHeight: 800
    }
  },
  computed: {
    // 画像のURLを取得しています
    imageUrl() {
      return this.$store.getters["images/getImageUrl"]
    },
    // ブロックの各頂点の座標を取得しています
    coordinate() {
      retun this.$store.getters["coordinates"/getCoordinate"]
    }
  },
  async mounted() {
    // DOMが生成されている必要があるのでmounted内で呼び出し
    await this.getImageUrl()
    this.draw()
  },
  methods: {
    async getImageUrl() {
      await this.$store.dispatch(
        "images/getImageUrl",
      )
    },
    draw() {
      // * $refsでcanvas要素を取得
      const canvas = this.$refs.canvas
      // 平面に描画するので2d
      const ctx = canvas.getContext("2d")
      // img要素を作成
      const img = document.createElement("img")
      // src属性にURLを指定
      img.src = this.imageUrl

      // 画像の読み込みが終わったあとに描画したいのでloadイベントのあとで処理
      img.addEventListener("load", () => {
        // 元の画像の横幅を取得
        const originalWidth = img.naturalWidth
        // 縮尺を計算
        const reducedScale = this.canvasWidth / originalWidth
        // 座標空間の変形
        // 変形を適用する場合には、先に変形を指定してから図形を描画するという順序になります。
        // 変形を後から指定しても、先に描画した図形には変形が適用されないので注意してください。
        ctx.scale(reducedScale, reducedScale)

        // 画像の描画、img要素と描画したい位置を指定
        ctx.drawImage(img, 0, 0)

        // 線の太さを指定
        ctx.lineWidth = 5
        // 線の色を指定
        ctx.strokeStyle = "#0f0"

        // 各頂点を取得
        cost xMax = this.Coordinate.x_max
        cost xMin = this.Coordinate.x_min
        cost yMax = this.Coordinate.y_max
        cost yMin = this.Coordinate.y_min
        // 新しいパスを開始
        ctx.beginPath()
        // パスの始点を移動
        ctx.moveTo(xMin, yMin)
        // どこまで線を引くか指定、右に移動
        ctx.lineTo(xMax, yMin)
        // 下に移動
        ctx.lineTo(xMax, yMax)
        // 左に移動
        ctx.lineTo(xMin, yMax)
        // パスを閉じる
        ctx.closePath()
        // 線を描画
        ctx.stroke()
      })
    }
  }
}
</script>

【Laravel】インストール時のエラー、zip展開できない

$ composer global require laravel/installer

とするとこんなエラーが出ました。

  Problem 1
    - laravel/installer v3.0.1 requires ext-zip * -> the requested PHP extension zip is missing from your system.
    - laravel/installer v3.0.0 requires ext-zip * -> the requested PHP extension zip is missing from your system.
    - Installation request for laravel/installer ^3.0 -> satisfiable by laravel/installer[v3.0.0, v3.0.1].


Installation failed, deleting ./composer.json.

laravel/installerPHPのzipの拡張機能を必要としているよ。

でもあなたのsystemにはそれが無いじゃない。

と言っているようです。

どうやらMacにプリインストールされているPHPではzipを展開するext-zipが入っていないらしい。

$ php --ri zip
Extension 'zip' not present.

たしかに無い。

HomebrewでPHPを再インストールします。

$ brew install php@7.3

パスを通します。

$ brew link php@7.3

ターミナルを再起動させて再度Laravelのインストールを行います。

$ composer global require laravel/installer

無事インストールされました。

`chmod`は`chage mode`という意味

$ chmod a+x /usr/local/bin/composer

chmodとはLinaxコマンドの一つ。

change modeという意味。パーミッションを設定する。

aすべての権限

x実行権限

a+xすべてのユーザに実行権限を与える

これで、いつでもcomposerを呼び出すことができる。