This is IT

技術、日常

【Ruby】Hashオブジェクトでもall?やnone?メソッドを使いたい

環境

WSL2 Debian12 bookworm Ruby 3.2.1

主題

値に真偽値を設定したHashオブジェクトがあるとする。

そのオブジェクトでも、all?none?を使って

{ bar: true, baz: true, foo: true }.all? #=> true
{ bar: false, baz: false, foo: false }.none? #=> true

とビシッと決めたい。

だが、all?none?をHashで使うには基本的にブロックが必要になる。

しかも、キーと値両方をブロック引数に渡さなければならない。

{ bar: true, baz: true, foo: true }.all? { |key, value| value } #=> true
{ bar: false, baz: false, foo: false }.none? { |key, value| value }#=> true

値の真偽値だけ知れればいいのに、keyもブロックに引き渡さなければならないのはちょっとダサい。

解決案

valuesメソッドを使う。

{ bar: true, baz: true, foo: true }.values.all? #=> true
{ bar: false, baz: false, foo: false }.values.none? #=> true

ビシッと決めた。

git branch --deleteと、git branch -D

mergeやpushをしていないブランチを削除するには、git branch -D branch_nameとする必要がある。

branchのdeleteオプション

-d, --delete          delete fully merged branch
-D                    delete branch (even if not merged)

訳すと、

-d, --delete          完全にマージされたブランチを削除する
-D                    ブランチを削除する (たとえマージされてなくても)

唐突な倒置法。

実際にpushやmergeをしていないブランチを、-dで削除しようとすると、

error: The branch 'my_wc_object' is not fully merged.
If you are sure you want to delete it, run 'git branch -D branch_name'.

と出る。

記事にするほどでもないけど手を動かしたくて書いた記事。

メソッドの引数にはハッシュオブジェクトではなく、キーワード引数を使う。

可読性を上げるためにもそうした方が良いという話。

引数がハッシュオブジェクト

まずはハッシュオブジェクトを渡す例

class Bar
  def initialize(options)
    @options = options
  end
end


def load_options
  {
    baz: true,
    foo: true,
    bee: true,
  }
end


options = load_options

bar1 = Bar.new(options)

この規模なら意味は分かる。でも例えばファイルが分かれてて、メソッドも見えなくなってしまい、

options = load_options

bar1 = Bar.new(options)

こんな感じになったら「load_optionsってどんなんや?」と探す手間が増えてしまう。 (まぁ何にせよ見に行くんだろうけど)

引数がキーワード引数

class Bar
  def initialize(baz: false, foo: false, bee: false)
    @baz = baz
    @foo = foo
    @bee = bee
  end
end

bar1 = Bar.new(baz: true, foo: true, bee: true)

同じようにファイルを分けてみても、

bar1 = Bar.new(baz: true, foo: true, bee: true)

インスタンス生成時に何をしたいかはよく分かる。

他にも、

bar2 = Bar.new()

のように、そもそも引数に何を渡すかを気にしない使い方をする処理ならば、デフォルト引数のおかげで楽に生成ができる。

前者の例だと、引数にoptionsに属するものを必ず渡す必要が出ちゃうから面倒くさいかも。

【JavaScript】ある月の最終日の日付を取得する方法

地味に混乱したためメモ書き。

getMonth()メソッド

まずは単純に現時点での日付・時刻を取得する方法。

var now = new Date();

console.log(now)

// 2023-07-14T11:41:20.194Z

これは特に問題がない。 JavaScriptでは、getMonth()メソッドでDateオブジェクトから「月」を取得できる。

var now = new Date();

console.log(now.getMonth())

// 6

現時点は「7月」のはずなのに、6が出力されてしまう。 これはgetMonth()は0を起点として月を返すため。

var now = new Date();

console.log(now.getMonth() + 1)

// 7

こうすることで欲しかった「7」を取得できる。

最終日の日付を取得

本題。 Date.new()では引数に何も渡さないと、インスタンス化された時点の日付、時刻を返す。

var now = new Date();

console.log(now)

// 2023-07-14T11:41:20.194Z

では、引数を渡すとどうなるのだろうか? new Date(year, monthIndex, day, hours, minutes, seconds, milliseconds)の順番で引数を渡せる。

var sample = new Date(1995, 11, 17, 18, 24, 0)

console.log(sample)

// 1995-12-16T18:24:00.000Z

繰り返しになるがDateオブジェクト初めの月の基準を0としているので、引数に11を渡したが、「12月」として返ってくる。

ここからが鍵となるが、Dateオブジェクトでは西暦や月、日付にありえない値を渡すとオーバーフロー、またはアンダーフローを起こす。

以下はMDN Web Docsの文章。

When a segment overflows or underflows its expected range, it usually "carries over to" or "borrows from" the higher segment. For example, if the month is set to 12 (months are zero-based, so December is 11), it become the January of the next year. If the day of month is set to 0, it becomes the last day of the previous month. This also applies to dates specified with the date time string format.

この中の例として挙げられている、

If the day of month is set to 0, it becomes the last day of the previous month.

には、日付に「0」を指定するとアンダーフローを起こし、前月の最終日を取得すると書いてある。 これを利用して、ある月の最終日を取得する。

例えば、2023年2月の最終日を取得するとしたら、

var sample = new Date(2023, 2, 0)

console.log(sample.getDate())

// 28

ナイス。

参考

Date.prototype.getMonth() Date() コンストラクター MDN Web Docs

ながーいメソッドになったら、一部を別メソッドに切り出そう

オブジェクト指向設計ガイドを読んでいて、依存を減らすテクニックの一つ。 今3章を読んでいるけど、『隠れた依存はあぶない』って感じの内容。

まずは簡単に依存が分かる例。

class Foo
  attr_reader :bar, :baz, :birthday

  def initialize(bar, baz, birthday)
    @bar = bar
    @baz = baz
    @birthday = birthday
  end

  def change
     ratio * alice.age ★
  end

  def ratio
    bar / baz
  end

  def alice
    @alice = Alice.new(birthday)
  end
end

class Alice
  attr_reader :birthday

  def initialize(birthday)
    @birthday = birthday
  end

  def age
    Data.today - birthday
  end
end

p Foo.new(10, 20, Alice.new('2000.01.01'))

オブジェクト指向関係なく、ざっくり処理の流れは分かる。 ★マークがついている処理も、「あ、aliceっていうインスタンス使っているんだな~」と分かる。つまり、他のクラスと依存関係にあることが把握できるので、変更があっても何とかなる。

でも例えば、★マークの処理がもっと複雑になると、、、

  def change
     bob.bar.ratio(baz, from) * (alice.age / years) ★
  end

もうこうなるとどれがインスタンスで、どれが自分のクラスで定義しているメソッドで・・・と処理を終えなくなる。

ここで著者はメソッドの抜き出しを薦めている。

すすめ

  def change
     calculate.ratio(baz, from) * divide ★
  end


  def calculate
    bob.bar
  end

  def divide
    alice.age / years
  end

メインの処理であるchabgeから、aliceとbobへの依存を切り出せた。changeメソッドの中の処理は、全てselfに送るメソッドだけになった。

初めの小さいコードのころは付け足していくだけでも何とかなったかもしれないけど、アプリが肥大化するにつれて目で追える量じゃなくなるかも。 全部が全部メソッドとして切り出せるわけではないけど、切り出せるか自問自答するのは一考の余地があるとのこと。

()はメソッドから離すな

以下の2つのコード。どちらがエラー出るコードか分かるでしょうか

# No.1
assert_equal ({ all_files: false, sort_in_reverse: false, long_format: false }, option.load)

# No.2
assert_equal({ all_files: false, sort_in_reverse: false, long_format: false }, option.load)

そうです。No.1がエラーとなります。

syntax error, unexpected ':', expecting '}'
assert_equal { all_files: false, sort_in_reverse: false, long_format: false }, option.load

理由はタイトルの通り。assert_equalだってメソッドです。

普段は気付けられるのですが、今回のようにMinitestであったり、フレームワークを利用して見知らぬメソッドを使うときには注意。

ActiveStorageを用いたファイルのアップロード・サムネイル生成の、各種gem・ライブラリを理解する

はじめに

現在通っているスクールの課題で、「ActiveStorageを用いて、ファイルのアップロード機能・サムネイル生成機能を作る」に取り組んでいます。

ActiveStorage単体だけではなく、Image_processingやmini_magick、ImageMagick、libvipsなどなど様々なライブラリやgemが存在し、大分頭がこんがらがってしまったので、一度整理をしておきます。

結論

  • ActiveStorage
    • アップロードや、ActiveRecortオブジェクトとの紐づけ。 画像処理は行えない。
  • ImageMagickとlibvips
  • ImageProcessing
    • ImageMagick・libvipsを用いた画像処理用のヘルパーメソッドを提供。MiniMagickと、RubyVipsも内包している高レベルのgem。
  • MiniMagickとRubyVips
    • ImageProcessingに内包されている。ImageProcessingで抽象化しきれなかった、各ツール(ImageMagick・libvips)の画像処理のヘルパーメソッドを提供。

ActiveStorageについて

ActiveStorageは、ストレージサービスへのファイルアップロードや、ActiveRecordオブジェクトとの紐づけを主としています。

Active Storageは、Amazon S3Google Cloud Storage、Microsoft Azure Storageなどのクラウドストレージサービスへのファイルのアップロードや、ファイルをActive Recordオブジェクトにアタッチする機能を提供します。

また、このフレームワーク自体では、画像のリサイズや各種画像処理を行うことはできません。

画像分析や画像加工のためにimage_processing gemも必要です。

Active Storage の概要

そのため、ActiveStorageを用いて画像をアップロードした後に、画像のリサイズなどの処理を行うには、各種gemやライブラリのインストールが必要となります。

ImageMagickとlibvipsについて

どちらも、コマンドラインから画像処理を行えるツールとなっています。

例えば、ImageMagickを用いて、「カレントディレクトリにあるPNGJPEGのファイルから100×100ドットのPNG形式のサムネイル画像を作ってファイル名の先頭をThumb-にする」といった処理ならば、以下のように記述をする必要があります。

magick *.png *.jpg -set filename:x "Thumb-%t" -thumbnail "100x100" "%[filename:x].png"


また、libvipsは、ImageMagickと用途は同じですが、ImageMagickに比べ、高速化・メモリ消費減を実現しているツールとなります。 画像処理を行いたい場合は、どちらかのツール(ライブラリ)のインストールが必須となります。

コマンドラインで画像処理が行える便利ツール「ImageMagick」

Active Storage の要件

ImageProcessingについて

ImageProcessingは、画像処理において必要な、高レベルな画像処理ヘルパーを提供してくれます。

ImageMagickや、libvips単体で用いた場合、画像処理に複雑なコードを自身で記述する必要がありますが、image_processingを用いることで画像処理ヘルパーが利用できるので、より簡易に処理コードを記述することができます。

また、このgemは、mini_magick・ruby-vips gemをそれぞれ内包しています。 image_processing gemをインストール後、Gemfile.lockを確認してみると、内包していることを確認できます。

image_processing Railsの画像まわりのライブラリについて整理する

MiniMagickとRubyVipsについて

上述の通り、それぞれimage_processing gemに内包されています。 それぞれ名前の通り、mini_magickはImageMagick用、ruby-vipsはlibvips用のヘルパーメソッドを用意してくれています。

例えば、画像サムネイルを表示させるviewファイルを作成する場合、app/views/users/show.html.erb内のコードは下記のようになります。

<p>
  <strong>Portrait:</strong>

  <%= image_tag @user.portrait.variant(メソッド) %>
</p>

ここでvariantメソッドの引数として渡したメソッドは、ImageProcessingライブラリ(高レベル)にメソッドが存在していれば、ImageProcessingのメソッドとして、存在しなければ、低レベルのMiniMagick、または、RubyVipsのメソッドとして実行されます。

ImageProcessing::MiniMagick ImageProcessing::Vips

パーフェクト Ruby on Rails 【増補改訂版】 (Perfect series)

処理の流れ

最後のまとめとして流れを記しておきます。

  1. ActiveRecordの機能を用いて、ファイルのアップロード
  2. ImageProcessingのヘルパーメソッドを用いて、画像の処理命令をImageMagick(libvips)に出す。
  3. ImageProcessingで包括しきれていない画像処理だった場合は、MiniMagick(RubyVips)が提供しているメソッドを利用して、処理命令を出す。
  4. ImageMagick(libvips)が実際に画像を処理