20190730のRubyに関する記事は16件です。

Ruby/Gtkの文字入力に自動補完機能をつける方法(Gladeなし縛り)

キーボード入力はミスが多い。
マウス入力は選択肢を見つける時間がもったいない。
そこで輝くのが「キーボードで絞り込んで、マウスで選択」という自動補完機能。
日本語情報が非常に少なかったので、投稿しておきます。

0:やりたい事

「["Tokyo","Yamagata","Yamaguchi","Yamanashi"]」という配列を渡し、
入力欄に「Y」と入れたら「["Yamagata","Yamaguchi","Yamanashi"]」の三択になり、
「Yaman」と入れたら「Yamanashi」一択になり、
「Yamanasi」(h忘れ)で0択になる。

縛り
Gtk3(3.3.6)のみ。Gladeは不使用で行きます。
筆者はWindows環境なので、Gladeを導入するだけで半泣きになりました。

1:まずは入力欄作成

Gtk::WindowとGtk::Entryを併用して無機能の入力欄を作ります。
ここまでは日本語情報もまあまああります。

auto_completion_sample.rb
require "gtk3"

class WindowSample < Gtk::Window
  def initialize
    super
    entry_sample=EntrySample.new#テキストエントリウィジェットを作成
    self.add(entry_sample)#ウィンドウにテキストエントリを追加する

    self.signal_connect('delete_event') {Gtk.main_quit()}#「このウィンドウが閉じられたらコード終了」の意味
    # この仕様があるせいで、最低一個はウィンドウを作る必要がある。全てダイアログのみのGUIを作る事は多分できない
  end
end

class EntrySample < Gtk::Entry
  def initialize()
    super
    array_sample=["Tokyo","Yamagata","Yamaguchi","Yamanashi"]#入力候補

    ####ここに自動補完処理を書く####

  end
end

window_sample= WindowSample.new()#ウィンドウを作成
window_sample.show_all()
Gtk::main()

若干コメントが多くてくどいですが、これは筆者のコーディングレベルに合わせたからです。

実行結果
キャプチ.PNG
白背景に白テキストエントリで見えづらいですね……。
当然、現段階では入力候補は何も出てきません。

2:ListStoreとは

GtkにはTreeModelというクラスがあります。どういうクラスなのかは……
よくわかりませんでした。誰か教えて下さい。
さてコメント稼ぎも済んだ所で、TreeModelクラスの一種にListStoreクラスがあります。
これは表(行列)のようなデータを扱うためのクラスです。
(詳しく知りたい人はTreeViewとかでググってください。ListStoreをTreeViewで表示する方法は、Qiita含めて日本語情報が結構あります。)
入力候補配列を、このListStoreクラスにします。

auto_completion_sample.rb
require "gtk3"

class WindowSample < Gtk::Window
  def initialize
    super
    entry_sample=EntrySample.new#テキストエントリウィジェットを作成
    self.add(entry_sample)#ウィンドウにテキストエントリを追加する

    self.signal_connect('delete_event') {Gtk.main_quit()}#「このウィンドウが閉じられたらコード終了」の意味
    # この仕様があるせいで、最低一個はウィンドウを作る必要がある。全てダイアログのみのGUIを作る事は多分できない
  end
end

class EntrySample < Gtk::Entry
  def initialize()
    super
    array_sample=["Tokyo","Yamagata","Yamaguchi","Yamanashi"]#入力候補

    store_sample = Gtk::ListStore.new(String)#「表は横一列。文字列のみ」と宣言。整数が2列だと(Integer,Integer)とかになる

    array_sample.each do |element_sample|
      store_sample.append[0]=element_sample#ListStoreの一番下に行を追加し、そのインデックスは0の列に配列の要素を代入。
      #エクセルと違い、ListStoreの列は0から始まります。
    end

    ####これでListStore完成####

  end
end

window_sample= WindowSample.new()#ウィンドウを作成
window_sample.show_all()
Gtk::main()

3:EntryCompletionとは

GtkにはEntryCompletionというクラスがあります
こちらは簡単ですね。テキストエントリの補完に使います。
そして、EntryCompletionクラスには「model」プロパティと「text_column」プロパティの2つを指定する必要があり、
このうち「model」プロパティはTreeModelクラスしか代入できません。
さっき配列をListStoreクラスにしたのはこれが理由です。

auto_completion_sample.rb
require "gtk3"

class WindowSample < Gtk::Window
  def initialize
    super
    entry_sample=EntrySample.new#テキストエントリウィジェットを作成
    self.add(entry_sample)#ウィンドウにテキストエントリを追加する

    self.signal_connect('delete_event') {Gtk.main_quit()}#「このウィンドウが閉じられたらコード終了」の意味
    # この仕様があるせいで、最低一個はウィンドウを作る必要がある。全てダイアログのみのGUIを作る事は多分できない
  end
end

class EntrySample < Gtk::Entry
  def initialize()
    super
    array_sample=["Tokyo","Yamagata","Yamaguchi","Yamanashi"]#入力候補

    store_sample = Gtk::ListStore.new(String)#「表は横一列。文字列のみ」と宣言。整数が2列だと(Integer,Integer)とかになる

    array_sample.each do |element_sample|
      store_sample.append[0]=element_sample#ListStoreの一番下に行を追加し、そのインデックスは0の列に配列の要素を代入。
      #エクセルと違い、ListStoreの列は0から始まります。
    end

    completion_sample= Gtk::EntryCompletion.new#作成して、
    completion_sample.model=store_sample#ListStoreを代入して、
    completion_sample.text_column=0#「ListStoreのうちインデックス0の列を使ってくださいね」と指定。一列しかないのになぜか必要。

    ####これでcompletionも用意完了####
  end
end

window_sample= WindowSample.new()#ウィンドウを作成
window_sample.show_all()
Gtk::main()

4:Entry.Comletionに代入

いよいよラスト一行。
EntryCompletionをEntry.comletionに代入します。

auto_completion_sample.rb
require "gtk3"

class WindowSample < Gtk::Window
  def initialize
    super
    entry_sample=EntrySample.new#テキストエントリウィジェットを作成
    self.add(entry_sample)#ウィンドウにテキストエントリを追加する

    self.signal_connect('delete_event') {Gtk.main_quit()}#「このウィンドウが閉じられたらコード終了」の意味
    # この仕様があるせいで、最低一個はウィンドウを作る必要がある。全てダイアログのみのGUIを作る事は多分できない
  end
end

class EntrySample < Gtk::Entry
  def initialize()
    super
    array_sample=["Tokyo","Yamagata","Yamaguchi","Yamanashi"]#入力候補

    store_sample = Gtk::ListStore.new(String)#「表は横一列。文字列のみ」と宣言。整数が2列だと(Integer,Integer)とかになる

    array_sample.each do |element_sample|
      store_sample.append[0]=element_sample#ListStoreの一番下に行を追加し、そのインデックスは0の列に配列の要素を代入。
      #エクセルと違い、ListStoreの列は0から始まります。
    end

    completion_sample= Gtk::EntryCompletion.new#作成して、
    completion_sample.model=store_sample#ListStoreを代入して、
    completion_sample.text_column=0#「ListStoreのうちインデックス0の列を使ってくださいね」と指定。一列しかないのになぜか必要。

    self.completion=completion_sample#完成したEntryCompletionをエントリのcompletionプロパティに代入

  end
end

window_sample= WindowSample.new()#ウィンドウを作成
window_sample.show_all()
Gtk::main()

5:実行確認とリファクタリング

キャプャ.PNG
キャチャ.PNG
キプチャ.PNG

挙動は完璧。
あとはこのカッコ悪い手続き型コードをリファクタリングするだけですが、
筆者はRuby歴2ヶ月のエクセルおじさんなので勘弁してください。

筆者の結論

良い子のみんなはGladeを使いましょう。

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

Ruby/Gtkの文字入力欄に自動補完を設定する方法(Gladeなし縛り)

キーボード入力はミスが多い。
マウス入力は選択肢を見つける時間がもったいない。
そこで輝くのが「キーボードで絞り込んで、マウスで選択」という自動補完機能。
日本語情報が非常に少なかったので、投稿しておきます。

0:やりたい事

「["Tokyo","Yamagata","Yamaguchi","Yamanashi"]」という配列を渡し、
入力欄に「Y」と入れたら「["Yamagata","Yamaguchi","Yamanashi"]」の三択になり、
「Yaman」と入れたら「Yamanashi」一択になり、
「Yamanasi」(h忘れ)で0択になる。

縛り
Gtk3(3.3.6)のみ。Gladeは不使用で行きます。
筆者はWindows環境なので、Gladeを導入するだけで半泣きになりました。

1:まずは入力欄作成

Gtk::WindowとGtk::Entryを併用して無機能の入力欄を作ります。
ここまでは日本語情報もまあまああります。

auto_completion_sample.rb
require "gtk3"

class WindowSample < Gtk::Window
  def initialize
    super
    entry_sample=EntrySample.new#テキストエントリウィジェットを作成
    self.add(entry_sample)#ウィンドウにテキストエントリを追加する

    self.signal_connect('delete_event') {Gtk.main_quit()}#「このウィンドウが閉じられたらコード終了」の意味
    # この仕様があるせいで、最低一個はウィンドウを作る必要がある。全てダイアログのみのGUIを作る事は多分できない
  end
end

class EntrySample < Gtk::Entry
  def initialize()
    super
    array_sample=["Tokyo","Yamagata","Yamaguchi","Yamanashi"]#入力候補

    ####ここに自動補完処理を書く####

  end
end

window_sample= WindowSample.new()#ウィンドウを作成
window_sample.show_all()
Gtk::main()

若干コメントが多くてくどいですが、これは筆者のレベルに合わせたからです。

実行結果
キャプチ.PNG
白背景に白テキストエントリで見えづらいですね……。
当然、現段階では入力候補は何も出てきません。

2:ListStoreとは

GtkにはTreeModelというデータ型があります。どういうデータ型なのかは……
よくわかりませんでした。誰か教えて下さい。
さてコメント稼ぎも済んだ所で、TreeModelの一種にListStoreがあります。
これはエクセルよろしく表(行列)のようなデータを扱うための型です。
(詳しく知りたい人はTreeViewとかでググってください。TreeViewについてはQiita含めて日本語情報が結構あります。)
入力候補配列を、このListStore型にします。

auto_completion_sample.rb
require "gtk3"

class WindowSample < Gtk::Window
  def initialize
    super
    entry_sample=EntrySample.new#テキストエントリウィジェットを作成
    self.add(entry_sample)#ウィンドウにテキストエントリを追加する

    self.signal_connect('delete_event') {Gtk.main_quit()}#「このウィンドウが閉じられたらコード終了」の意味
    # この仕様があるせいで、最低一個はウィンドウを作る必要がある。全てダイアログのみのGUIを作る事は多分できない
  end
end

class EntrySample < Gtk::Entry
  def initialize()
    super
    array_sample=["Tokyo","Yamagata","Yamaguchi","Yamanashi"]#入力候補

    store_sample = Gtk::ListStore.new(String)#「表は横一列。文字列のみ」と宣言。整数が2列だと(Integer,Integer)とかになる

    array_sample.each do |element_sample|
      store_sample.append[0]=element_sample#ListStoreの一番下に行を追加し、そのインデックスは0の列に配列の要素を代入。
      #エクセルと違い、ListStoreの列は0から始まります。
    end

    ####これでListStore完成####

  end
end

window_sample= WindowSample.new()#ウィンドウを作成
window_sample.show_all()
Gtk::main()

3:EntryCompletionとは

GtkにはEntryCompletionというデータ型があります。どういうデータ型なのかは……
こちらは簡単ですね。テキストエントリの補完に使います。
そして、EntryCompletionは「model」プロパティと「text_column」プロパティの2つを指定する必要があり、
このうち「model」プロパティはTreeModel型しか代入できません。
さっき配列をListStoreにしたのはこれが理由です。

auto_completion_sample.rb
require "gtk3"

class WindowSample < Gtk::Window
  def initialize
    super
    entry_sample=EntrySample.new#テキストエントリウィジェットを作成
    self.add(entry_sample)#ウィンドウにテキストエントリを追加する

    self.signal_connect('delete_event') {Gtk.main_quit()}#「このウィンドウが閉じられたらコード終了」の意味
    # この仕様があるせいで、最低一個はウィンドウを作る必要がある。全てダイアログのみのGUIを作る事は多分できない
  end
end

class EntrySample < Gtk::Entry
  def initialize()
    super
    array_sample=["Tokyo","Yamagata","Yamaguchi","Yamanashi"]#入力候補

    store_sample = Gtk::ListStore.new(String)#「表は横一列。文字列のみ」と宣言。整数が2列だと(Integer,Integer)とかになる

    array_sample.each do |element_sample|
      store_sample.append[0]=element_sample#ListStoreの一番下に行を追加し、そのインデックスは0の列に配列の要素を代入。
      #エクセルと違い、ListStoreの列は0から始まります。
    end

    completion_sample= Gtk::EntryCompletion.new#作成して、
    completion_sample.model=store_sample#ListStoreを代入して、
    completion_sample.text_column=0#「ListStoreのうちインデックス0の列を使ってくださいね」と指定。一列しかないのになぜか必要。

    ####これでcompletionも用意完了####
  end
end

window_sample= WindowSample.new()#ウィンドウを作成
window_sample.show_all()
Gtk::main()

4:Entry.Comletionに代入

いよいよラスト一行。
EntryCompletionをEntry.comletionに代入します。

auto_completion_sample.rb
require "gtk3"

class WindowSample < Gtk::Window
  def initialize
    super
    entry_sample=EntrySample.new#テキストエントリウィジェットを作成
    self.add(entry_sample)#ウィンドウにテキストエントリを追加する

    self.signal_connect('delete_event') {Gtk.main_quit()}#「このウィンドウが閉じられたらコード終了」の意味
    # この仕様があるせいで、最低一個はウィンドウを作る必要がある。全てダイアログのみのGUIを作る事は多分できない
  end
end

class EntrySample < Gtk::Entry
  def initialize()
    super
    array_sample=["Tokyo","Yamagata","Yamaguchi","Yamanashi"]#入力候補

    store_sample = Gtk::ListStore.new(String)#「表は横一列。文字列のみ」と宣言。整数が2列だと(Integer,Integer)とかになる

    array_sample.each do |element_sample|
      store_sample.append[0]=element_sample#ListStoreの一番下に行を追加し、そのインデックスは0の列に配列の要素を代入。
      #エクセルと違い、ListStoreの列は0から始まります。
    end

    completion_sample= Gtk::EntryCompletion.new#作成して、
    completion_sample.model=store_sample#ListStoreを代入して、
    completion_sample.text_column=0#「ListStoreのうちインデックス0の列を使ってくださいね」と指定。一列しかないのになぜか必要。

    self.completion=completion_sample#完成したEntryCompletionをエントリのcompletionプロパティに代入

  end
end

window_sample= WindowSample.new()#ウィンドウを作成
window_sample.show_all()
Gtk::main()

5:実行確認とリファクタリング

キャプャ.PNG
キャチャ.PNG
キプチャ.PNG

挙動は完璧。
あとはこのカッコ悪い手続き型コードをリファクタリングするだけですが、
筆者はRuby歴2ヶ月のエクセルおじさんなので勘弁してください。

筆者の結論

これだけの事を調べるのに20時間とかかけても何の意味もありません。
良い子のみんなはGladeを使いましょう。

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

Rubyワンライナーメモ : 文字列の先頭を小文字にする

1ライナーで完結する書き方をやってる人が検索で出てこなかったので。
tapとsub!を組み合わせると出来ます。

downcase.rb
"HogeHoge".tap{ |s| s.sub!(s[0], s[0].downcase) }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsのMVCをまとめてみる

はじめに

RailsはMVCアーキテクチャを採用しています。
MVCアーキテクチャに基づき、Railsはどのような流れで処理を行っているのかを確認していこうと思います。

注意すること

調べていて分かったことなのですが、どうやら
RailsはMVCではない
のだそうです。正しくは
RailsはMVC2である
そうです。
(Model2MVCやModel2など、他にも呼び方があるみたいです)

「Rails MVC」と「MVC」で検索して理解を深めようとした結果、
分かったような分かっていないような
というモヤモヤした気持ちになった方もいるのではないでしょうか。

今回はMVCとMVC2の違いについてはまとめませんが、
MVC?あぁRailsのやつだよね?
という理解は厳密には間違っているそうですので、先に記述しておきます。

RailsのMVC

MVCは次の3つの要素を指します。

  • Model(モデル)
  • View(ビュー)
  • Controller(コントローラー)

Rails_MVC.png

それぞれの役割は次のようになります。

Model

データベースを管理し、検索・挿入・更新・削除などを行います。

View

Webページ上でどのように表示するかが定義されています。

Controller

ModelとViewに指示を出します。
Modelから必要な情報を取得し、それらをもとにViewにWebページを構築させます。

また、RailsではControllerでの処理をアクションと呼び、
複数定義することができます。

処理の流れ

Model・Controller・Viewは次の順番で処理されます。

Rails_MVC_Order.png

もう少し具体的に処理を書きだすと

  1. 指定されたControllerの、指定されたアクションが起動
  2. ControllerはModelを通してデータベースとやりとりする
  3. データベースから取得したデータを元に、ControllerはViewにWebページを構築させる
  4. Viewの内容をレスポンスとして返す

といった流れとなります。

ルーティングについて

routes.rbファイルに定義された通り、
リクエストされたURLとControllerのアクションを結びつけます。
地図みたいなものですね。
MVC_URL_Routes_MVC.png

おわりに

各ファイルの役割や処理の流れが整理できていなかったのですが、
投稿するためにまとめることで多少理解できたかなと思います。
コーディングしているときに迷わないよう、常に処理を意識しながら作業していこうと思います。

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

【初心者向け・動画付き】Railsで日時をフォーマットするときはstrftimeよりも、lメソッドを使おう

はじめに:日時の表示に関してよくある問題

何も考えずにViewにcreated_atのような日付を出力すると、「あれっ?」と思うような表示になることがあります。

app/views/users/index.html.erb
<% @users.each do |user| %>
  <tr>
    <td><%= user.name %></td>
    <!-- 作成日時を表示する -->
    <td><%= user.created_at %></td>
    <td><%= link_to 'Show', user %></td>
    <td><%= link_to 'Edit', edit_user_path(user) %></td>
    <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>

Screen Shot 2019-07-30 at 20.47.50.png

具体的には以下の2つのポイントが「あれっ?」と思う点だと思います。

  • 日本時間(JST)ではなく、世界標準時(UTC)で表示されてしまう
  • "Tue, 30 Jul 2019 00:12:19 +0000"のようなフォーマットは日本人にとって馴染みがない

この記事ではこの問題を解決する方法を紹介します。

タイムゾーンを日本時間に変更する

最初にタイムゾーンを日本時間にしましょう。
config/application.rbに以下の設定を追加し、サーバーを再起動してください。

config/application.rb
module TimeFormatSandbox
  class Application < Rails::Application
    # ...

    # タイムゾーンを日本時間に設定
    config.time_zone = 'Asia/Tokyo'
  end
end

これで画面に表示された日時が日本時間になります。

Screen Shot 2019-07-30 at 20.52.31.png

日本人が読みやすい日時フォーマットにする

続いて、日時の表示形式を日本人が読みやすいフォーマットに変更しましょう。

strftimeでも変更できるが、あまりオススメできない

ネットを検索すると以下のようにstrftimeメソッドを使って検索する方法がよく出てきます。

<%= user.created_at.strftime('%Y/%m/%d %H:%M:%S') %>

もちろんこれでも目的は達成できるのですが、他にも日時を表示するViewがあると、'%Y/%m/%d %H:%M:%S'のような書式文字列を繰り返し書かないといけないため、コードがDRYになりません。

DRYでないコードは変更に弱いコードになります。
変更に弱いコードは良くないコードです。

lメソッドでDRYに書式を指定する(オススメ)

Railsにはlメソッドという便利なメソッドがあるので、これを活用しましょう。

以下はlメソッドの使用例です。

<!-- lメソッドを使って書式を指定する -->
<td><%= l user.created_at %></td>

ただし、lメソッドを使うだけでは何も変化がありません。

Screen Shot 2019-07-30 at 20.52.31.png

lメソッドを活用するには、もう少し作業が必要です。

次に行うのはロケールの設定です。
今回は日本人が読みやすい書式にするのが目的なので、アプリケーションのロケールを:jaに設定します。

config/application.rbに以下の設定を追加してください。

config/application.rb
module TimeFormatSandbox
  class Application < Rails::Application
    # ...

    # デフォルトのロケールを日本(ja)に設定
    config.i18n.default_locale = :ja
  end
end

続いて、config/locales/ja.ymlというファイルを作成し、以下のような設定を記述します。

config/locales/ja.yml
ja:
  time:
    formats:
      default: "%Y/%m/%d %H:%M:%S"

上の設定は日時(time)のデフォルトの書式を%Y/%m/%d %H:%M:%Sにするための設定です。

これでサーバーを再起動すると、日本人向けの書式で日時が表示されます。

Screen Shot 2019-07-30 at 21.33.47.png

strftimeメソッドとは異なり、l foo.created_atのような記述でどのViewでも同じ書式が得られるため、コードもDRYになります。

Tips: i18n_generatorsでja.ymlを自動生成する

上で作成したconfig/locales/ja.ymlは、i18n_generators gemを使って自動生成すると便利です。
このgemの使い方は以下のとおりです。

まず、Gemfileにi18n_generatorsを追加し、bundle installを実行します。

Gemfile
group :development do
  # ...

  gem 'i18n_generators'
end

次にターミナルから以下のコマンドを実行します。

rails g i18n_locale ja

config/locales/ja.ymlが生成されるので、このファイルを開いてja > time > formats > defaultの書式文字列を編集します。
デフォルトの書式文字列は "%Y年%m月%d日(%a) %H時%M分%S秒 %z" になっているので、これを要件に合わせて変更してください。

なお、i18n_generatorsが生成したja.ymlには他にも日本語ロケール向けの設定がたくさん定義されています。
i18n_generatorsに関する詳しい情報はREADMEを参照してください。

https://github.com/amatsuda/i18n_generators

Tips: View以外ではI18n.lを使う

Viewではlと書くだけでOKですが、lメソッドがヘルパーメソッドとして提供されていない場所(Modelなど)では、lだけではNoMethodErrorになります。
その場合は、lの代わりにI18n.lと書けば、日時をフォーマットすることができます。

ちなみにllocalizeのエイリアスメソッドなので、localizeと書いても構いません。

以下の記述はどれも同じ結果になります。

= l user.created_at
= I18n.l user.created_at
= localize user.created_at
= I18n.localize user.created_at

追記:Time::DATE_FORMATS[:default]を変更するのもオススメしない

日時のフォーマットを変更する方法としてもうひとつ、Time::DATE_FORMATS[:default]の設定を変えるという方法もあるようです。

config/initializers/time_formats.rb
Time::DATE_FORMATS[:default] = '%Y/%m/%d %H:%M:%S'

こうすると、lメソッドもstrftimeも使わずに書式を統一することができます。

<!-- 何もしなくても書式が'%Y/%m/%d %H:%M:%S'になる -->
<td><%= user.created_at %></td>

これだけ見ると「すごく便利じゃん!」と思うかもしれません。
ですが、アプリケーション全体のデフォルト設定が変わってしまうため、予期しない問題を引き起こすかもしれません。
実際に問題が発生する事例は以下の記事で紹介されています。

RailsのTime::DATE_FORMATS[:default]は変更しないほうがいい - Qiita

ですので、この方法もあまりオススメできません。

動画はこちら

この記事の内容はYouTube動画としてアップしています。
動画を見たい方は以下のリンクから視聴してください。

https://youtu.be/QUnWDvPONAo

参考文献

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

rubyでスターリンソートをやってみた(ブロック渡しも可能)

今話題のスターリンソートruby で実装してみました。

準備

stalin_sort.rb
class Array
  # Githubの実装と同様
  def stalin_sort
    return [] if empty?

    max = first
    select do |x|
      next if max > x

      max = x
      x
    end
  end

  # ブロックあり
  def stalin_sort_by(&block)
    return [] if empty?

    max = yield(first)
    map { |x| [yield(x), x] }
      .select { |y, _x| next if max > y; max = y }
      .map    { |_y, x| x }
  end
end

Githubの実装Array を引数に取り実行していますが、 ruby の良さを活かすためクラス拡張に変更しました。

実行

[1, 2, 1, 1, 4, 3, 9].stalin_sort
# => [1, 2, 4, 9]

names = %w(alice bob ava benjamin carol)

# ブロックなし
names.stalin_sort
# => ["alice", "bob", "carol"]

# ブロックあり
names.stalin_sort_by(&:length)
# => ["alice", "benjamin"]

簡単になりますが以上です。
掲載したものより良いコードがあれば是非ともお願いします!

引用元

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

cannot load such file -- bcrypt というエラー

passwordを暗号化したいために
gem bcryptをbundle installした後、
ブラウザを再起動したときに出たエラー

原因はよくわからないですが、rails serverを再起動することで直りました。
定期的にrails serverは再起動した方がいいんですかねぇ、他にもこれを再起動することで直ったエラーがあったような

参考URL↓
http://tusukuru.hatenablog.com/entry/2016/08/24/160059

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

printf("%02d", "08") でエラー

Rubyの printf には次のような仕様があることを知りました。

printf("%02d", "07") # "07"
printf("%02d", "08") # ArgumentError / invalid value for Integer(): "08"

数値を表す書式(この例の"d")があるときに、埋め込まれるものが文字列だった場合は、数値に変換されるというものです。"07" は8進数の7です。"08" は0で始まっているのに8進数の書式としておかしいのでエラーになります。

C、Java、Pythonで試してみたところ、数値の書式に対応するものが文字列のときは必ずエラーになりました。PerlとPHPでは、数値の書式に対応する文字列は10進数として変換されます。

Rubyのソースコードでは、この仕様にあたる部分はsprintf.cの rb_str_format にありました(ver 2.5.2)。

    switch (*p) {
(略)
      case 'd':
(略)
        {
        volatile VALUE val = GETARG();
(略)

        switch (TYPE(val)) {
(略)
          case T_STRING:
            val = rb_str_to_inum(val, 0, TRUE);
            goto bin_retry;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Raspberry Pi上でbundle installをして無理だった話

なぜRaspberry Piでrails?

家のPCがSurface Proなのだが、Surface ProはVirtual Boxが入らないので仮想環境でLinuxを動かせず、デュアルブートをするほどストレージに空き領域もないため仕方なくRaspberry Piで環境を構築したのである。

発生したエラー

Gemfileに「gem install bcrypt」を追加し、
>> bundle install
を実行したとき

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




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

となった。
エラーメッセージの通りに
>> gem install bcrypt -v '3.1.13' --source 'https://rubygems.org/'
を実行する
すると、、、

Building native extensions. This could take a while...
ERROR: Error installing bcrypt:
ERROR: Failed to build gem native extension.




make "DESTDIR="
compiling bcrypt_ext.c
compiling crypt_blowfish.c
gcc -D__SKIP_GNU -I/home/pi/.rbenv/versions/2.5.3/include -D_FILE_OFFSET_BITS=64 -c -o x86.o x86.S
x86.S: Assembler messages:
x86.S:202: Error: junk at end of line, first unrecognized character is `,'
<builtin>: recipe for target 'x86.o' failed
make: *** [x86.o] Error 1

原因

エラーを見てわかる人もいるかと思うが、どうやらgem installで落としてくるパッケージにはrubyで書かれておらずx86用のCPUのマシンで動作するc言語で書かれたネイティブコードがあるらしい。
今回は「bcrypt」がそれに該当、、、

さすがにこれをRaspberry PiのARM用にクロスコンパイルするパワーが無く断念

解決策

本来はRaspberry Piの上で開発などする人がいないかと思うが、、、同じような人がいたら

解決策としては
①自分でRaspberry PiのARM用にクロスコンパイルする
②別の環境でinstallする(他のx86が載っているPC環境かクラウド環境)

組み込みエンジニアの見解としては断然②のほうが楽だと思われます笑
①でできた人がいたらそのファイルくださいね笑

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

Rails minitestでconcernsのテストを書く

はじめに

concernsで実装はすっきりしたんだけど、minitestでテストを書くときにどうするんだっけ?と困った。rspecに入れ替えてshared_examples使おうという声が聞こえるけどたぶん錯覚なんだ。

実装サンプル

こんな感じのサンプルがありまして...

class SampleModel < ActiveRecord::Base
  include SampleConcern

  # name attribute defined
end
module SampleConcern
  extend ActiveSupport::Concern

  included do
    # concernsに実装するのはたぶん不適切だけど
    validate name, presence: true
  end

  def greet
    "I'm #{name}"
  end
end

テスト

SampleConcernActiveRecordに依存しない(has_manyとかvalidatesが書いていない)場合はただのmoduleなので、テスト用クラスを作ってincludeしてやれば済む。
大人の事情により、依存しているクラスをテストする場合は以下のコードでテストが書ける。
(module側のsetupincludedの中に定義することでテストクラスのsuper呼び出しを省略できるがケースバイケース)

require 'test_helper'
require 'models/sample_concern_test'

class SampleModelTest < ActiveSupport::TestCase
  include SampleConcernTest

  def setup
    super
  end

  private

  def test_instance
    SampleModel.new
  end
end
module SampleConcernTest
  extend ActiveSupport::Concern

  def setup
    @my_sample = test_instance
  end

  included do
    test "correct name greeting" do
      name = "paty"
      @my_sample.name = name

      assert_equal "I'm #{name}", @my_sample.greet
    end
  end

  private

  def test_instance
    raise NotImplementedError.new("override with InheritanceClass.new")
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】form_for 練習用に超簡単なミニアプリを作成する

はじめに

form_for系の練習のため、最小構成で登録系のアプリを作成。
後は個人で適当にカスタマイズ。

仕様

  • viewは情報登録画面(/view/user/new.html.erb)のみを定義。
  • アカウント名、メールアドレス、パスワードのみをuserTBLに登録するフォームを設置。
  • 情報登録後は情報登録画面(/view/user/new.html.erb)にリダイレクトさせる。

画面イメージ

スクリーンショット 2019-07-29 21.36.22.png

ルーティングは以下のみ定義。

routes.rb
Rails.application.routes.draw do
  root 'users#new'
  resources :users, only: [:new, :create]
end

userTBL

column
name string
email string
password string

構築手順

アプリケーションをmysqlで作成
$ rails new rails_practice -d mysql -T
アプリケーションのディレクトリへ移動
$ cd rails_practice
DB作成
$ rake db:create
usersコントローラ作成
$ rails g controller users

以下の通り編集

users_controller.rb
class UsersController < ApplicationController

    class UsersController < ApplicationController

    def new
        @user = User.new
    end

    def create
        @user = User.new(user_params)
        @user.save
        redirect_to :root
    end

    private
        def user_params
            params.require(:user).permit(:name, :email, :password)
        end

end

userモデル作成
$ rails g model user

rails g model user コマンドで生成されたマイグレーションファイルを以下の通り編集

20190729115345_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email
      t.string :password
      t.timestamps
    end
  end
end
マイグレートコマンドを実行
$ rails db:migrate

以下の通りなればOK
スクリーンショット 2019-07-29 20.59.06.png

ビュー作成
view/users/new.html.erb
<head>
    <meta charset="utf-8">

</head>
<h1>
    会員登録
</h1>
<body>
    <%= form_for(@user, url: users_path, method: :post) do |f| %>
        <p>
            <%= f.label :name, "アカウント名" %><br>
            <%= f.text_field :name %>
        </p>
        <p>
            <%= f.label :email, "メールアドレス" %><br>
            <%= f.email_field :email %>
        </p>
        <p>
            <%= f.label :password, "パスワード" %><br>
            <%= f.password_field :password %>
        </p>
        <%= f.submit "登録する"%>
    <% end %>
</body>
ルーティング設定
routes.rb
Rails.application.routes.draw do
  root 'users#new'
  resources :users, only: [:new, :create]

end


動作確認

入力

スクリーンショット 2019-07-30 7.43.45.png

DBへの登録を確認

スクリーンショット 2019-07-30 7.44.09.png

DBへの登録後、リダイレクト

スクリーンショット 2019-07-30 7.43.29.png

以上

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

rails カラムの値 一斉変更

1日1回回答できるクイズの残回答数を24時にリセットするバッチにて用いた

app/models/quiz.rb
class Quiz < ActiveRecord::Base
  def self.reset_quota
    QuizUserStatus.all.update_all(quota: 1)
  end
end

なお、条件次第で残1以上になるため今回はこのような実装にした

以上。

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

ruby match 正規表現、大文字小文字区別しない

以下AndroidであればPlayStore、iosであればAppStoreにリダイレクトさせるロジックの一部

if ua.match(/android/i) != nil
  redirect_to "https://play.google.com/store/apps/details?id=hoge"
elsif ua.match(/ios/i) != nil || ua.match(/iphone/i) != nil || ua.match(/ipad/i) != nil
  redirect_to "https://itunes.apple.com/th/app/fuga"
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Capistranoで、EC2+S3への自動デプロイで起きたエラー集と基礎用語

はじめに

メルカリクローンアプリの作成でグループ開発でデプロイを担当しました。
今回はほぼ前提知識ない中、疑問に思った用語とエラーの解決へのプロセス、そして今回意識した事を備忘録とチームメンバーとの共有資料として記載しておきます。

環境
Ruby → 2.5.1
Rails → 5.2.3
Capistrano → 3.11.0
carrierwave

用語

用語の説明ですが、100%正しい知識ではないかも知れないです。と言うのも、もっと深い知識を知るともっと差別化しないと状況がごちゃごちゃになると思いますが、今回は小規模での開発だったので最低限の知識で乗り切りました。
私は今後必要になったら、詳細について調べていくつもりです。もっと詳細に知りたい人は随時調べてみてください。

●ssh

EC2に入るとき使っている下記のコマンド

$ ssh -i キーの名前.pem ec2-user@00.000.0.00

Secure Shellの略。リモートコンピュータと通信するためのプロトコルです。
ざっくり鍵を使って暗号化と復号で認証作業をしています。
さらっとやったchomd600は読み込み、書き込み権限の付与をしています。

●sudo

スーパーユーザーでdoする。スーパーユーザーとはrootの事です。
rootユーザーは権限が強過ぎるのでコマンド単位だけその力を使おうと言うコマンド。

●mysqld

dはデーモンの略です。mysqldはmysqlからデータを持ってきたりしてくれるものと言う認識です。
デーモンは常に待機状態でいるプログラムです。

●nginx

webサーバーの一つ。webサーバーとはリクエストを受け取り、レスポンスを返す役割を請け負っています。
nginxの特徴が気になる人は別途調べてみてください。

●tail

logを見るときに使用。最後の10行が表示される。-fのオプションを使用する事でログを垂れ流しに出来る。
個人的には、あまり使いませんでした。

●cat

ファイルの中身を見る事が出来る。logやエラーが起きた箇所の本番環境での記述を見るために使った。-nオプションで行数を表示出来るので見やすい.

●RAILS_ENV

動作環境を指定したい時に使用。環境がどうこうのエラーが出た時に確認したり、場合によっては環境を指定してあげないとエラーが出る事があったので使った。

エラーへの対処

まず最初に特に何もしていない部分のエラーが起きた時は、EC2インスタンスの停止→起動をするのが得策です。
無料枠を使っているせいなのか、期待通りの動きをしてくれない事があるらしいです。(真偽は確認していませんが、何日か空けて自動デプロイしようとすると大体エラーが出ていました。仕様と諦めていたので、わかる方教えてください)
なので、心辺りのないエラーや自動デプロイが止まる時は再起動をしてみて、それでも起こるならエラーに対処をしてくのが時間効率がいいと思います。
では、私がハマったり頻度の高かったエラーを書いていきます。

Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

(2)の場合はsocketがないので

$ touch mysql.sock

で、解決出来ると思います。ないなら作ってやる、それだけです。ここから発展して(38)に変わる事があるようですが、それは私は出会わなかったので、割愛します。

Can't connect to local MySQL server through socket '/tmp/mysql.sock' (111)

検索してみると、権限がない事に起因するエラーの様ですが、私の場合には当てはまりませんでした。mysqlにアクセス出来ない旨のエラーなのでひとまず

$ mysql -u root -p

でひとまずルートに入る事が出来るが確認してみる→出来なかったら検索
入れたらsocketの指定をしているファイル(database.yml)の設定を見る
それも合っていたら、環境変数を指定してみる

$ rake db:migrate RAILS_ENV=production

私はこれで解決しました。
他にも、mysql.sockが消えていない為、新しいmysql.sockが生成出来ず起動できないみたいな事もある様です。
この辺の解決策は他の方の記事を参考に出来ると思うのでそちらを見てみてください。

credentials.yml.enc

ここは一番苦労しました。ただ参考資料が沢山のあるので、状況を整理しながらやっていけば問題なく出来ると思います。
チーム開発で起こっていたミスとしては。
・master→rails newした人とデプロイ担当が違う為、master.keyが複数ある環境で開発を進めていた
・APIkeyを書く人が、自分のmaster.keyで新しいcredentials.ymlを作ってしまったファイルをmargeして本番環境で開けなくなった
などです。
暗号化されている為コンフリクトが起きた時にわかりにくいので、一度丁寧に確認するのが良いと思います。
今回のチーム開発では初期段階でmaster.keyの共有をして、使わないkeyは消してしまうのが得策と思います。

以下、大変参考になった記事です。
https://qiita.com/NaokiIshimura/items/2a179f2ab910992c4d39
https://qiita.com/yuuuking/items/53a37a2e998972be32b8

まとめ

チーム共有がメインの記事なので、分かりにくいところもあると思いますが、デプロイ担当で思ったことは、どの環境で、どのコマンドを、何に対してやったのかをわかった上で作業を進めるのが大切だと思いました(当たり前ですが)。
自分でも良くわからない状況で、適当にコマンドを叩いていると周りもフォローしにくくなりますし、泥沼にハマる確率が高くなります(体験談)。
頑張れ!デプロイ担当!
※口頭説明しながら前提で作ったのと、知識不足で憶測や間違った解釈を書いている可能性が高いので後日書き足すか、限定公開にする予定です。

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

VimmerがRuby on Railsを書くときに使えるプラグイン集 2019

vimでRuby on Railsを書くときに重宝するプラグインを紹介します。

  • NERDTree   ファイルツリー
  • vim-rails    ファイル移動、Railsコマンド実行
  • fzf.vim     インクリメンタル検索
  • vim-endwise  ifdef などの end 補完
  • coc.nvim    補完
  • ale       静的解析
  • ruby-matchit  % によるdef...end等のキーワードを移動

設定例として、自分の.vimrcに書いている設定を載せてます。

NERDTree

scrooloose/nerdtree
ファイルツリー機能が使えます。ド定番です。
nerdtree2.mov.gif

参考
vim-plugin NERDTree で開発効率をアップする! @zwirky

vim-rails

tpope/vim-rails

railsプロジェクト内のシームレスな移動や、vimからのrailsコマンドの実行が出来るようになります。
vimrails.mov.gif

GIFでは例として、gfでカーソル化の文字列から該当するファイルを開き、:Aで対応するspecファイルを開いています。

参考
vim-rails よく使いそうなやつ @bibio

fzf.vim

junegunn/fzf.vim

Go言語製の fzf と呼ばれるコマンドラインツールを使用したプラグインで、プロジェクト内のファイル名や、ソースコードの文字列あいまい検索ができます。

.vimrc
let g:fzf_action = {
  \ 'ctrl-s': 'split' }

nnoremap <C-p> :FZFFileList<CR>
command! FZFFileList call fzf#run(fzf#wrap({
            \ 'source': 'find . -type d -name .git -prune -o ! -name .DS_Store',
            \ 'down': '40%'}))

nnoremap <C-b> :Buffers<CR>
nnoremap <C-g> :Ag<CR>
nnoremap <silent> <C-]> :call fzf#vim#tags(expand('<cword>'))<CR>

let g:fzf_buffers_jump = 1

command! -bang -nargs=? -complete=dir Files
            \ call fzf#vim#files(<q-args>, fzf#vim#with_preview(), <bang>0)

fzf.mov.gif

GIFでは ctrl + p でファイル名検索、ctrl + bでバッファー検索をしています。
ctagsとの連携なんかもできちゃいます。

fzf.vim はプラグイン本体に加えて fzfそのもののインストールも必要になりますが、
fzfvim 抜きにしてもコマンドラインツールとしてとても優秀なのでオススメです。

参考
fzfとvimで少ない労力で作業効率を引き上げた話 @Sa2Knight
さいつよのターミナル環境を構築しよう @b4b4r07

ファイル検索プラグインの別の選択肢としてdenite.nvim(unite.vimのつよいやつ)というプラグインがあります。
もっと複雑な処理を走らせたい方におすすめです。(あんまりわかってない)

vim-endwise

tpope/vim-endwise
def...endや、if...endなどの対応するキーワードを自動補完してくれます。

endwise.mov.gif

地味にきいてくる便利さです。

coc.nvim

neoclide/coc.nvim
coc.nvimTypeScript 製の補間プラグインです。LanguageServerProtocol(以下LSP)と呼ばれる、コーディング支援用のプロトコルが使用可能で、強力な補完機能が使えます。
ruby の場合、 solargraphと呼ばれるLSPの gem を使用します。

coc.nvim は、 独自の設定ファイルとして .vim/coc-settings.json を使用します。

coc-settings.json
{
  "suggest.enablePreselect": true,
  "solargraph.commandPath": "solargraphの絶対パス"
}
.vimrc
let g:coc_global_extensions = ['coc-solargraph']

inoremap <silent><expr> <TAB>
      \ pumvisible() ? "\<C-n>" :
      \ <SID>check_back_space() ? "\<TAB>" :
      \ coc#refresh()
inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<C-h>"

function! s:check_back_space() abort
  let col = col('.') - 1
  return !col || getline('.')[col - 1]  =~# '\s'
endfunction

coc.mov.gif

右に[LS]と表記されている単語が、LSPによる補完候補です。
めっちゃ出ます。

参考
LanguageServerProtocol(LSP)のススメ @himanoa
neovim + coc.nvim で LSP 藻ログ

coc.nvim の他にも、asyncomplete + vim-lsp や、 deoplete + LanguageClient-neovim の組み合わせでもLSPの補完が使えます。

ale

w0rp/ale

gemrubocop構文エラーを自動で非同期静的解析してくれるツールです。

.vimrc
let g:ale_fixers = {
      \ 'ruby': ['rubocop'],
      \ }

ale.mov.gif

if...endendを消すと構文エラーを指摘してくれます。

言語ごとにfix, lintツールを指定するとruby以外でも大活躍してくれます。

参考
neovim + ?ale + ?rubocop hoshinotsuyoshi.com

ruby-matchit

vim-scripts/ruby-matchit
%を拡張して、def...end等のキーワードを移動出来るようになります。

まとめ

他にも良いプラグインがあれば教えてください
それではよい vim, rails ライフを…

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

VimmerがRuby on Railsを書くときに使えるVimプラグイン集 2019

vimでRuby on Railsを書くときに重宝するプラグインを紹介します。

  • NERDTree   ファイルツリー
  • vim-rails    ファイル移動、Railsコマンド実行
  • fzf.vim     インクリメンタル検索
  • vim-endwise  ifdef などの end 補完
  • coc.nvim    補完
  • ale       静的解析

設定例として、自分の.vimrcに書いている設定を載せてます。

NERDTree

scrooloose/nerdtree
ファイルツリー機能が使えます。ド定番です。
nerdtree2.mov.gif

参考
vim-plugin NERDTree で開発効率をアップする! @zwirky

vim-rails

tpope/vim-rails

railsプロジェクト内のシームレスな移動や、vimからのrailsコマンドの実行が出来るようになります。
vimrails.mov.gif

GIFでは例として、gfでカーソル下の文字列から該当するファイルを開き、:Aで対応するspecファイルを開いています。

参考
vim-rails よく使いそうなやつ @bibio

fzf.vim

junegunn/fzf.vim

Go言語製の fzf と呼ばれるコマンドラインツールを使用したプラグインで、プロジェクト内のファイル名や、ソースコードの文字列あいまい検索ができます。

.vimrc
let g:fzf_action = {
  \ 'ctrl-s': 'split' }

nnoremap <C-p> :FZFFileList<CR>
command! FZFFileList call fzf#run(fzf#wrap({
            \ 'source': 'find . -type d -name .git -prune -o ! -name .DS_Store',
            \ 'down': '40%'}))

nnoremap <C-b> :Buffers<CR>
nnoremap <C-g> :Ag<CR>
nnoremap <silent> <C-]> :call fzf#vim#tags(expand('<cword>'))<CR>

let g:fzf_buffers_jump = 1

command! -bang -nargs=? -complete=dir Files
            \ call fzf#vim#files(<q-args>, fzf#vim#with_preview(), <bang>0)

fzf.mov.gif

GIFでは ctrl + p でファイル名検索、ctrl + bでバッファー検索をしています。
ctagsとの連携なんかもできちゃいます。

fzf.vim はプラグイン本体に加えて fzfそのもののインストールも必要になりますが、
fzfvim 抜きにしてもコマンドラインツールとしてとても優秀なのでオススメです。

参考
fzfとvimで少ない労力で作業効率を引き上げた話 @Sa2Knight
さいつよのターミナル環境を構築しよう @b4b4r07

ファイル検索プラグインの別の選択肢としてdenite.nvim(unite.vimのつよいやつ)というプラグインがあります。
もっと複雑な処理を走らせたい方におすすめです。(あんまりわかってない)

vim-endwise

tpope/vim-endwise
def...endや、if...endなどの対応するキーワードを自動補完してくれます。

endwise.mov.gif

地味にきいてくる便利さです。

coc.nvim

neoclide/coc.nvim
coc.nvimTypeScript 製の補間プラグインです。LanguageServerProtocol(以下LSP)と呼ばれる、コーディング支援用のプロトコルが使用可能で、強力な補完機能が使えます。
ruby の場合、 solargraphと呼ばれるLSPの gem を使用します。

coc.nvim は、 独自の設定ファイルとして .vim/coc-settings.json を使用します。

coc-settings.json
{
  "suggest.enablePreselect": true,
  "solargraph.commandPath": "solargraphの絶対パス"
}
.vimrc
let g:coc_global_extensions = ['coc-solargraph']

inoremap <silent><expr> <TAB>
      \ pumvisible() ? "\<C-n>" :
      \ <SID>check_back_space() ? "\<TAB>" :
      \ coc#refresh()
inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<C-h>"

function! s:check_back_space() abort
  let col = col('.') - 1
  return !col || getline('.')[col - 1]  =~# '\s'
endfunction

coc.mov.gif

右に[LS]と表記されている単語が、LSPによる補完候補です。
めっちゃ出ます。

参考
LanguageServerProtocol(LSP)のススメ @himanoa
neovim + coc.nvim で LSP 藻ログ

coc.nvim の他にも、asyncomplete + vim-lsp や、 deoplete + LanguageClient-neovim の組み合わせでもLSPの補完が使えます。

ale

w0rp/ale

gemrubocop構文エラーを自動で非同期静的解析してくれるツールです。

.vimrc
let g:ale_fixers = {
      \ 'ruby': ['rubocop'],
      \ }

ale.mov.gif

if...endendを消すと構文エラーを指摘してくれます。

言語ごとにfix, lintツールを指定するとruby以外でも大活躍してくれます。

参考
neovim + ?ale + ?rubocop hoshinotsuyoshi.com

まとめ

他にも良いプラグインがあれば教えてください
それではよい vim, rails ライフを…

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