RailsのArrayの拡張メソッド「inquiry」

こんにちは!kossyです!




さて、今回はRailsのArrayの拡張メソッドであるinquiryの使い方について、ブログに残してみたいと思います。



使い方

pets = [:cat, :dog].inquiry

pets.cat?     # => true
pets.ferret?  # => false

pets.any?(:cat, :ferret)  # => true
pets.any?(:ferret, :alligator)  # => false

引用: [https://api.rubyonrails.org/classes/Array.html#method-i-inquiry]

公式ドキュメントから引用しました。

配列に対してinquiryメソッドを実行し、変数に代入します。

すると、配列の要素をメソッドとして変数に対して使えるようになります。
上記の例で言うと、pets変数に対してcat?と実行すると、pets変数がcatを持っていればtrueが返り、持っていなければfalseが返ります。


また、inquiryメソッドを実行すると、anyメソッドも使えるようになります。


使いどころがいまいち浮かびませんが、
勉強になりました。




参考にさせていただいた記事
qiita.com

Rubyの標準モジュール「NKF」で全角英数字を半角に変換する

こんにちは!kossyです!




さて、今回はRubyの標準モジュールである「NKF」を用いて、
全角英数字や全角スペースを半角に変換する方法をブログに残してみたいと思います。





環境
Rails 6.0.2.1
Ruby 2.5.1
MacOS Mojave




方法

こんな感じのCSVファイルがあったとします。
f:id:kossy-web-engineer:20200221205618p:plain

で、このファイルの中身を全て半角でDBに取り込んで欲しいという要件があったとします。


そういう時に使えるのが、NKFモジュールです。

$ rails c

$ require 'csv'

$ require 'nkf'

$ csv = CSV.parse(NKF.nkf('-w -Z1',File.read('./tmp/members.csv')), headers:true)

$ csv.headers
=> ["ID", "NAME"]

$ csv.map do |row|
>    {'1': row[0], '2': row[1] }
>  end

=> [{:"1"=>"001", :"2"=>"TANAKA TARO"},
 {:"1"=>"002", :"2"=>"YAMADA ZIRO"},
 {:"1"=>"003", :"2"=>"SATO SABURO"},
 {:"1"=>"004", :"2"=>"ITO SIRO"},
 {:"1"=>"005", :"2"=>"MOURI KOGORO"},
 {:"1"=>"006", :"2"=>"TAKAHASHI ROKURO"},
 {:"1"=>"007", :"2"=>"SUZUKI SHICHIRO"},
 {:"1"=>"008", :"2"=>"YAMAMOTO HATIROU"},
 {:"1"=>"009", :"2"=>"NAKAGAWA KURO"}]

tmpディレクトリ以下のmembers.csvnkfメソッドにオプション-w と -Z1を付与して読み込みを行なっています。

  • wオプションは文字コードUTF-8で出力することを指定します。
  • Z1オプションは全角英数字や記号、全角スペースを半角に変換するオプションです。

上記のオプションを指定してCSVの読み込みを行なった結果、
["ID", "NAME"] のように半角に変換されました。

NKFには他にもたくさん使い方や便利なオプションがあるので、興味のある方は
公式ドキュメントを参考にしてみてください。

docs.ruby-lang.org

rails r でターミナルからRailsのコードを実行する

こんにちは!kossyです!




さて、今回はrails r コマンドでターミナルからRailsのコードを実行する時のTIPSをご紹介できればと思います。




環境
Rails 6.0.2.1
Ruby 2.5.1
MacOS Mojave




クラスの継承順位を出力する


例えばRailsアプリケーションにBookモデルが定義されているとします。

app/models/book.rb

class Book < ApplicationRecord
end

この場合、ターミナルで、

$ bundle exec rails r "puts Book.new.class.ancestors"

Book
Book::GeneratedAssociationMethods
Book::GeneratedAttributeMethods
ApplicationRecord
ApplicationRecord::GeneratedAssociationMethods
ApplicationRecord::GeneratedAttributeMethods
ActiveRecord::Base
ActionText::Attribute
ActiveStorage::Reflection::ActiveRecordExtensions
ActiveStorage::Attached::Model
GlobalID::Identification
ActiveRecord::Suppressor
ActiveRecord::SecureToken
ActiveRecord::Store
ActiveRecord::Serialization
ActiveModel::Serializers::JSON
ActiveModel::Serialization
ActiveRecord::Reflection
ActiveRecord::NoTouching
ActiveRecord::TouchLater
ActiveRecord::Transactions
ActiveRecord::NestedAttributes
ActiveRecord::AutosaveAssociation
ActiveModel::SecurePassword
ActiveRecord::Associations
ActiveRecord::Timestamp
ActiveRecord::Callbacks
ActiveRecord::AttributeMethods::Serialization
ActiveRecord::AttributeMethods::Dirty
ActiveModel::Dirty
ActiveRecord::AttributeMethods::TimeZoneConversion
ActiveRecord::AttributeMethods::PrimaryKey
ActiveRecord::AttributeMethods::Query
ActiveRecord::AttributeMethods::BeforeTypeCast
ActiveRecord::AttributeMethods::Write
ActiveRecord::AttributeMethods::Read
ActiveRecord::Base::GeneratedAssociationMethods
ActiveRecord::Base::GeneratedAttributeMethods
ActiveRecord::AttributeMethods
ActiveModel::AttributeMethods
ActiveModel::Validations::Callbacks
ActiveRecord::DefineCallbacks
ActiveRecord::Locking::Pessimistic
ActiveRecord::Locking::Optimistic
ActiveRecord::AttributeDecorators
ActiveRecord::Attributes
ActiveRecord::CounterCache
ActiveRecord::Validations
ActiveModel::Validations::HelperMethods
ActiveSupport::Callbacks
ActiveModel::Validations
ActiveRecord::Integration
ActiveModel::Conversion
ActiveRecord::AttributeAssignment
ActiveModel::AttributeAssignment
ActiveModel::ForbiddenAttributesProtection
ActiveRecord::Sanitization
ActiveRecord::Scoping::Named
ActiveRecord::Scoping::Default
ActiveRecord::Scoping
ActiveRecord::Inheritance
ActiveRecord::ModelSchema
ActiveRecord::ReadonlyAttributes
ActiveRecord::Persistence
ActiveRecord::Core
ActiveSupport::Dependencies::ZeitwerkIntegration::RequireDependency
ActiveSupport::ToJsonWithActiveSupportEncoder
Object
PP::ObjectMixin
JSON::Ext::Generator::GeneratorMethods::Object
ActiveSupport::Tryable
ActiveSupport::Dependencies::Loadable
Kernel
BasicObject

このような実行結果が得られます。

Railsは裏でめちゃめちゃいろんなコードが動いているのを実感できますね、、、



勉強になりました。

Rubyのnet/httpsでGoogleBooksAPIを叩いてみる

こんにちは!kossyです!



さて、今回はRubyの標準モジュールであるnet/httpsを使って、
GoogleBooksAPIを使う方法について、ブログに残してみたいと思います。

環境
Ruby 2.6.3
Rails 6.0.2.1
MacOS Mojave





GoogleBooksAPIから返却されるJSONを見てみる

下記のURLにアクセスしてみてください。
https://www.googleapis.com/books/v1/volumes?q=rails


JSON形式の文字が羅列されたページが表示されたかと思います。


GoogleBooksAPIで検索を行う場合、

https://www.googleapis.com/books/v1/volumes?q=検索したい語句

のように、クエリ文字列に検索したい語句を指定することで、JSON形式のレスポンスを受け取ることができます。
このレスポンスをよしなに加工して使ってみたいと思います。


コード全晒し

# lib/google_book_client.rb

require 'net/https' # モジュールの読み込み

class GoogleBookClient # クラスとして定義
  include Singleton # デザインパターンの一種。GoogleBookClient.instance.search(keyword)のように外部から呼び出す

  def initialize
    @uri = URI.parse 'https://www.googleapis.com/books/v1/volumes' # URIにparseする
    @http = Net::HTTP.new @uri.host, @uri.port # httpインスタンスを生成
    @http.use_ssl = true # httpsで通信を行うようにする。指定しないとエラーする場合がある
  end

  def search(keyword) # 検索を行って配列を返すメソッド
    create_request(keyword)

    @res['items'].map do |item|
      info = item['volumeInfo']
      {
        id: item['id'],
        title: info['title'],
        price: item['saleInfo']['listPrice'].nil? ? ' - ' : item['saleInfo']['listPrice']['amount'].floor.to_s,
        authors: info['authors'],
        publisher: info['publisher'],
        published_date: info['publishedDate'],
        description: info['description'],
        image_path: info['imageLinks']['smallThumbnail']
      }
    end
  end

  private

    def create_request(keyword) # httpリクエストを投げてレスポンスを返り値とするメソッド
      req = Net::HTTP::Get.new @uri.path + "?q=#{keyword}" # getリクエストを生成
      _res = @http.request req # httpリクエストを投げる
      @res = JSON.parse(_res.body) # レスポンスをJSON形式にparseする
    end

end

Railsの場合、このファイルをディレクトリに置いて、config/application.rbに

module MyApp
  class Application < Rails::Application
    config.load_defaults 6.0
    config.paths.add 'lib', eager_load: true # <= を追加
  end
end

上記のようにlibディレクトリのファイルをロードするように設定すれば、
コンソールで試せるようになるかと思います。

早速コンソールで試してみます。

$ cd ~/任意のRailsアプリのディレクトリ

$ bundle exec rails c 

> GoogleBookClient.instance.search('Rails')

=> [{:id=>"jqKkDwAAQBAJ",
  :title=>"独習Ruby on Rails",
  :price=>"3960",
  :authors=>["小餅 良介"],
  :publisher=>"翔泳社",
  :published_date=>"2019-06-19",
  :description=>
   "現場で使える Ruby on Rails 5.2の基本 プログラミング言語RubyによるWebアプ
リケーション開発の フレームワーク「Ruby on Rails」が、『独習』シリーズに登場!
 Rails入門者だけでなく、プログラミング初心者も、 ・解説→コード→演習 という形
式で、自力で使えるようになるまで、 基礎から一通り学べる本格入門書。 Railsを実
際に教えている著者による、 オブジェクト指向から、MVCモデルまで、 しっかり、じ
っくり学べる一冊です。 ~~~目次~~~ Chapter 1 Rails概要 Chapter 2 オブジェクト
指向とRubyの基本 Chapter 3 Railsの起動と簡単なアプリケーションの構築 Chapter
4 Rails全体の仕組み Chapter 5 Active Record Chapter 6 モデルに実装すべき役割
Chapter 7 モデルを豊かにする仕組み Chapter 8 ルーターとコントローラー Chapter
 9 コントローラーによるデータの扱い Chapter 10 Action View Chapter 11 ビュー
を支える機能 Chapter 12 その他のコンポーネント Chapter 13 Active SupportとRai
lsのテスト ※本電子書籍は同名出版物を底本として作成しました。記載内容は印刷出
版当時のものです。 ※印刷出版再現のため電子書籍としては不要な情報を含んでいる
場合があります。 ※印刷出版とは異なる表記・表現の場合があります。予めご了承く
ださい。 ※プレビューにてお手持ちの電子端末での表示状態をご確認の上、商品をお
買い求めください。 (翔泳社)",
  :image_path=>
   "http://books.google.com/books/content?id=jqKkDwAAQBAJ&printsec=frontcover
&img=1&zoom=5&edge=curl&source=gbs_api"},
...

上記のような出力結果が得られると思います。
あとは返ってきた値をviewで使用すればOKです。

ActiveRecordのfindとArrayのfind

こんにちは!kossyです!




さて、今回はActiveRecordのfindとArrayのfindがどのようにして挙動を切り替えているか、
気になったので調べてみました。



ブロックの有無で分岐していた

# activerecord/lib/active_record/relation/finder_methods.rb

def find(*args)
  return super if block_given?
  find_with_ids(*args)
end

もしブロックが渡されていれば、Arrayクラスのfindを使い、そうでなければActiveRecordのfindを呼び出しているようです。

なので、

User.find(params[:id])

は、ブロックが渡されている訳ではないのでActiveRecordのfindメソッドが実行され、

User.all.find { |user| user.created_at == Date.today }

は、ブロック引数 {} が渡されているので、Arrayのfindメソッドが実行されるというわけですね。


コード規約を定める場合は、Arrayのfindメソッドのエイリアスである「detect」を使うようにする、
という取り決めをしてもいいかもしれませんね。



勉強になりました。

ubuntu環境の日本語化

こんにちは!kossyです!




さて、今回はvirtualbox + vagrantubuntu環境を構築する際に、出力を日本語化する手順について、
ブログに残してみたいと思います。
なお、virtualbox + vagrant のダウンロードについては割愛します。

実行手順

まずはbox add を実行します。

$ vagrant box add ubuntu/bionic64

通信環境によっては時間がかかるのでじっくり待ちましょう。

ダウンロードが終わったら、

$ vagrant init ubuntu/bionic64

$ vagrant up

$ vagrant ssh

ubuntu環境にssh接続します。

ssh接続に成功したら、仮想環境上で、
以下のコマンドを順に実行します。

$ sudo locale-gen ja_JP.UTF-8

$ echo export LANG=ja_JP.UTF-8 >> ~/.profile

$ source ~/.profile

$ sudo timedatectl set-timezone Asia/Tokyo # タイムゾーンを日本標準時に変更する


これで日本語化できます。

キャメルケースの文字列をスネークケースに変換するActive Support::Inflectorの「underscore」メソッド

こんにちは!kossyです!




さて、今回はキャメルケースの文字列をスネークケースに変換する「underscore」というメソッドの使い方を
ブログに残してみたいと思います。




Active Support::Inflectorってなに?

ActiveSupport::Inflector によると、

The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept in inflections.rb.

The Rails core team has stated patches for the inflections library will not be accepted in order to avoid breaking legacy applications which may be relying on errant inflections. If you discover an incorrect inflection and require it for your application or wish to define rules for languages other than English, please correct or add them yourself (explained below).

Inflectorは、単語を単数形から複数形に、クラス名をテーブル名に、モジュール化されたクラス名をなしに、クラス名を外部キーに変換します。
複数形化、単数化、および数えられない単語のデフォルトの語形変化は、inflections.rbに保持されます。

Railsコアチームは、誤った語形変化に依存している可能性のあるレガシーアプリケーションの破損を避けるため、語形変化ライブラリのパッチは受け入れられないと述べています。
誤った語形変化を発見し、それをアプリケーションに必要とする場合、または英語以外の言語の規則を定義する場合は、それらを自分で修正または追加してください(以下で説明します)。

単語の形式をよしなに変形してくれる、Railsの便利モジュールという認識で良さそうです。


使い方

使い方は至って簡単です。

$ 'RubyOnRails'.underscore

=> "ruby_on_rails"

キャメルケースの文字列を_で区切った文字列に変換してくれます。



勉強になりました。



参考にさせていただいたサイト

ActiveSupport::Inflector
[Rails5] Active Support::Inflectorの便利な活用形メソッド群|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社

Railsのenumで定義したシンボルを元に文字列を返すメソッドを定義する

こんにちは!kossyです!




さて、今回はRailsenumで定義したシンボルを元に文字列を返すメソッドを定義したいと思います。
enumの詳しい説明は割愛します。



実装方法

class User

  enum role: [:general, :admin]

end

Userクラスがroleというカラムを持っていて、値をenumで管理している例です。

この場合、view側に「一般ユーザー」または「管理者」という表示をしたいという要望が出た場合、どう実装すればいいでしょうか。

app/views/users/show.html.haml

  = current_user.role == :general ? '一般ユーザー' : '管理者'

みたいな感じでしょうか。
これでもいいかと思いますが、enumのシンボルの値を4,5つと定義した場合は、実装がややこしくなります。


そこで、

class Item

  enum status: [:not_ordered, :ordered, :pick_up, :delevered, :unknown]

  def status_str
    { not_ordered: '未発注', ordered: '発注済', pick_up: '配達中', delevered: '配達済み', unknown: '未確認' }[status.to_sym]
  end

end

こうすれば、

@item.status_str # @item.status == :ordered

=> 発注済

のように、文字列を取り出すことができます。

ng-container で不要なタグを出力するのを防ぐ

こんにちは!kossyです!



さて、今回はAngularのディレクティブの一つであるng-containerの使い方について、
ブログに残してみたいと思います。



ng-containerってなに?

Angular日本語ガイドによると、

は Angular パーサーによって認識される構文要素です。 ディレクティブ、コンポーネント、クラス、またはインターフェースではありません。 JavaScript の if ブロックの中括弧のようなものです

引用元:
Angular 日本語ドキュメンテーション

何ができるの?

タイトルにもありますが、不要なタグが出力されなくなります。

qiita.com


divタグの中でngForをするとdivタグも繰り返し出力されてしまいますが、
ng-containerを使えばそれを防ぐことができます。

公式ドキュメントによると、

あるとき、あなたは指定した条件が true なものだけで HTML ブロックを繰り返したいと思います。 あなたは同じホスト要素に *ngFor と *ngIf の両方を配置 しよう とするでしょう。 Angular はそれを許しません。1つの要素に適用できる構造ディレクティブは1つだけです。

その理由は単純です。構造ディレクティブは、ホスト要素とその子孫を使って複雑なことを行えます。 2つのディレクティブが同じホスト要素を要求するとき、どちらが優先されるでしょうか? NgIf と NgFor のどちらが先になるでしょうか。 NgIf は NgFor の効果をキャンセルできるでしょうか?もしそうなら (そしてそれはそうあるべきだと思われる)、Angular は他の構造ディレクティブをキャンセルする能力をどのように一般化すべきでしょうか?

これらの質問に対する簡単な答えはありません。複数の構造ディレクティブを禁止することでその質問を無意味にします。 このユースケースでは簡単な解決策があります。*ngFor 要素をラップするコンテナ要素に *ngIf を追加します。 一方または両方の要素をng-containerにすることができるので、余分な階層の HTML を導入する必要はありません。

出典:
Angular 日本語ドキュメンテーション

ngForとngIfを両方使いたいがために余計にdivを定義する必要はありませんよ、ってことですね。




勉強になりました。

実務に入る前に知りたかったRails周りのことをまとめてみる