20200525のRubyに関する記事は26件です。

正規表現の基礎

概要

irbを使って正規表現について見ていきます。


irb

irb(main):001:0> name = "taro"
=> "taro"
irb(main):002:0> name.sub(/taro/,"kotaro")
=> "kotaro"

subメソッドを使ってtaroという文字をkotaroに置き換えています。
第1引数に置き換えたい文字列を指定し、第2引数に変換後の文字列を記述します。


irb(main):004:0> name.match(/taro/)
=> #<MatchData "taro">
irb(main):005:0> name.match(/bob/)
=> nil

matchメソッドを使って引数に指定した文字列が含まれているかをチェックしています。
含まれていた場合は指定した文字列がMatchDataオブジェクトとして返り値で返ってきます。
含まれていなかった場合はnilが返ってきます。

irb(main):006:0> array = name.match(/taro/)
=> #<MatchData "taro">
irb(main):007:0> array[0]
=> "taro"

MatchDataオブジェクトは配列なので、上記のようにすると値を取得することができます。

irb(main):008:0> phoneNumber = "080-1234-5678"
=> "080-1234-5678"
irb(main):009:0> phoneNumber.gsub(/-/,"")
=> "08012345678"

電話番号のハイフンを取り除きたい場合ですが、subメソッドを使ってしまうと最初のハイフンしか置換されないので、gsubメソッドを使いましょう。gとはグローバルマッチで、指定した文字列が複数含まれている場合全て置換してくれます。

irb(main):010:0> myPassword = "Taro0123"
=> "Taro0123"
irb(main):012:0> myPassword.match(/[a-z\d]{8,10}/i)
=> #<MatchData "Taro0123">
irb(main):013:0> myPassword.match(/[a-c\d]/i)
=> #<MatchData "a">
irb(main):014:0> myPassword.match(/[a-c\d]{8,}/i)
=> nil

・[a-z]は、a~zの文字がいずれか1個にマッチ
・\dは、数字にマッチ
・{8,10}は、直前の文字列が少なくとも8回、多くても10回出現するものにマッチ
・iは、大文字と小文字を区別しないで検索する
という意味です。

irb(main):015:0> myAddress = "yey@gmail.jp"
=> "yey@gmail.jp"
irb(main):016:0> myAddress.match(/@.+/)
=> #<MatchData "@gmail.jp">
irb(main):017:0> 

メールアドレスのドメインを取得する場合
.は、どの1文字にもマッチ
+は、直前の文字の1回以上の繰り返しにマッチ
という意味を持ちます。
ですので@.+ とすると
@から始まり、全ての文字にマッチ、それを繰り返す。ということになるので
@以降のドメインが取得できます。

この記事を読んでいただきありがとうございました。

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

【Rails】コメントされると投稿が削除できない!?

投稿の経緯

Twitter、FbのようなSNSを作成中のことです。
投稿機能に削除ボタンを付けたのですが…投稿が消せない!!

もっと細かく言うと、ただの投稿は消せる。
投稿に写真を載せたり(postモデルとimageモデルで分けている場合に限る)
投稿にコメントをされたりすると消せなくなる。。

【本日の登場人物】
(親)postモデル →投稿を保存するモデル
(子)imageモデル →投稿に紐づく画像を保存するモデル
(子)commentモデル →投稿に対するコメントを保存するモデル

つまり、postモデルにネストしている子モデル(imageモデル、commentモデル)に値が入るとpostレコードを削除できなくなってしまう訳ですね。

実際のコードを見てみましょう。

実際のコード

post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :comments
  has_many :images
end

何の変哲もないpostモデルのアソシエーションですね。
ただ、このままだと、子モデルに値が入ると親モデルが消せなくなります。
なぜなら、親が消えてしまったら、子の親がいなくなってしまうから。。当たり前ですね笑

つまり、親を削除した時に、子も一緒に削除される設定をしてしまえばいいんです。

解決策

モデルで「dependent: :destroy」を設定してあげましょう。
dependent…翻訳すると「依存」ですね。イメージ湧きやすいかと思います。

これをアソシエーションの時に設定されたモデルは、そのクラスが削除されると、一緒に削除されるようになります。
何言ってるかわからないですね、実際に下に書いてみます。

解決後のコード

post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :comments,  dependent: :destroy
  has_many :images,    dependent: :destroy
end

何となくわかりましたかね?
Postクラスimageモデルcommentモデルに設定していますね。
postモデル(投稿)が削除されるとアソシエーションでdependent: :destroyを設定されたモデル(画像、コメント)も一緒に削除される訳ですね。

注意点

これに関して注意してもらいたいことがあります。
実はこのdependent: :destroyって子モデルを削除するための設定じゃないんです。

アソシエーションを削除するための設定なんです。

例えば、commentモデルに以下のとおり記載してみます。

comment.rb
belongs_to :post, dependent: :destroy

こうなってしまうと、コメントを削除したら投稿まで削除されてしまいます。

もう一度いいます。
アソシエーションの時に設定されたモデルは、そのクラスが削除されると、一緒に削除されるようになります。

まとめ

今回も私の拙い説明を最後まで読んでいただきありがとうございました。
間違いや補足がありましたらコメントいただけると嬉しいです。

どんどはれ。

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

Railsの基本理念

Railsの生みの親DHHが書いた基本理念をまとめました

https://rubyonrails.org/doctrine/#optimize-for-programmer-happiness
https://postd.cc/rails-doctrine/

1. プログラマの幸福度を最適化

DHHがRailsを作った理由は「自分が笑顔になるため」

論理的ではなくむしろ病的だが、自分が幸福になるために作ってきた

Railsのコミュニティは幸福のために集まり、幸福の最適化によってRailsは形成されてきた

2. 設定より規約 (Convention over Configuration、CoC)

Railsの生産性に関する初期のモットーは「無駄な個性をやめれば、平凡な決断で苦労することなく、重要な場面で仕事のスピードが増す」データベースの主キーがidか、postIdか、posts_idか、pidか議論するのは無駄設定から規約に移行することで、無駄な熟考を避け生産性を高めることができる

加えて、規約は初心者のハードルを下げてくれるRailsの規約の中には初心者が知らなくてもいいものがたくさんある彼らにはその無知ゆえのメリットがある全てを知らずともすばらしいアプリケーションを作ることができるフレームワークがただの分厚い教科書であればそれは不可能

そうすると、「全て既存のテンプレートで作れる」と思いがちだが、たいていは5%や1%でも独自の要素を持つ価値がある

規約からいつ逸脱するかは難しい逸脱したいという衝動のほとんどは熟考されておらず、レールを外れて行くことのコストは過小評価されている

  • コメント

あまり気にしたことなかったですが、Ruby on Rails の名前は「規約 (Rail)に沿ったRuby」って意味なんでしょうねhttps://jp.quora.com/Ruby-Rails-no-namae-no-yurai-wo-oshie-te-kuda-sai

3. メニューはおまかせで

プログラミングにとって、他の人に任せることの利点は、設定より規約から得られるものと似ているが、より高いレベルにある。CoCが個々のフレームワークをどのように使うのがベストなのかを考えるのに対し、おまかせはどのフレームワークをどのように組み合わせて使うのかを考える。これは、利用可能なツールを個々の選択肢として提示するという崇高なプログラミングの伝統に反するしかし、その伝統が個々のプログラマに決定する特権(と重荷)を与えてきた「仕事に最適な道具を使え」という言葉は議論の余地がないほど初歩的なことだが、「最高の道具」を選ぶには、「最高」を自信を持って判断できる土台が必要であり、これは思った以上に難しい。

そこで私たちは、Railsでは、全員にとって良いツールボックスのために、箱の中の各ツールを選ぶというプログラマーの個人的な特権を1つ減らすことにした。その結果、多くの利益を得ることができた。

1.みんなが使っていれば安全

ほとんどの人が同じデフォルトでRailsを使っているとき、経験を共有できる。共通のベースがあると、人に教えたり助けたりするのが格段に楽になる。それはより強いコミュニティの感覚を育む。

2.共通の基本的なツールボックスを人々が進化させる

フルスタックフレームワークとして、Railsは動的な部分も数多くありますし、それぞれが単独でどのように機能するのかと同じくらいに、それらがどのように組み合わさって機能するのかというのが重要になる。ソフトウェアの不具合はそれぞれのコンポーネントが原因ではなく、それらの相互作用の中で発生する。構成された複数のコンポーネントから、共通の不具合を緩和しようとみんなで取り組み、同じ状態で失敗するならば、不具合の回数を減らすことができる。

3.交換は可能だが、必須ではない

Railsは“おまかせ“スタックである一方で、他のフレームワークやライブラリと取り替えることもできる。しかし、必須ではない。つまり、たまに発生する相違に適した、明確で個人的なパレットをのちに開発することもできる。経験もあり腕のあるプログラマでさえ、メニューの中全てのものを気に入らないことは十分にある(もし、全てに満足している人がいるならば、それはRailsでまだ行き詰まっていないのでしょう)。彼らは勤勉にも別のものに交換し、それからみんなが監督し共有している他のスタックにいってみて、楽しんでいる。

4. パラダイムは1つではない

「Railsは多くの異なる考え方からできている」以外何も分からなかった…

5. 美しいコードを称える

私たちは、コンピュータや他のプログラマーに理解されるためだけにコードを書くのではなく、美しさの温かな光を浴びるためにコードを書きます。美しいコードはそれ自体が価値であり、精力的に追求されるべきものです。だからといって、美しいコードが常に他の事に勝るというわけではありませんが、優先順位には必ずあるべきです。

class Project < ApplicationRecord
    belongs_to :account
    has_many :participants, class_name: 'Person'
    validates :name, presence: true
end

これは DSL のように見えますが、実際にはただのクラス定義で、シンボルとオプションを取る 3 つのクラスメソッドを呼び出しています。派手なものは何もありません。しかし、確かにきれいで、シンプルです。この数少ない宣言から、膨大な量のパワーと柔軟性を得ることができます。

美しさの一部は、これらの呼び出しが「設定より規約」を尊重していることから来ています。belongs_to :accountを呼び出すとき、外部キーはaccount_idと呼ばれ、それがProjectsのテーブルにあることを前提としています。Personのクラス名をparticipantsのアソシエーションの役割に指定する必要がある場合、そのクラス名の定義のみが必要です。そこから外部キーと他の設定が得られます。

class CreateAccounts < ActiveRecord::Migration
  def change
    create_table :accounts do |t|
      t.integer :queenbee_id
      t.timestamps
    end
  end
end

これがフレームワークの力の本質です。プログラマは特定の規約(例えば、#changeを実装したActiveRecord::Migrationのサブクラス)に従ってクラスを宣言します。そうするとフレームワークは、それをひと通り全部調べて、これが呼び出すメソッドだと認識します。

これにより、プログラマは書くべきコードが非常に少なくなります。マイグレーションの場合、rails db:migrateを呼び出してDBをアップグレードして新しいテーブルを追加するだけでなく、rails db:rollbackでこのテーブルを削除することもできます。これは、プログラマーがこれらのことを実現するために、ライブラリを使ってワークフローをつなぎ合わせるのとは大きく異なります。

6. 鋭いナイフを提供する

Rubyは機能の中に鋭いナイフをたくさん含んでいます。それは偶然ではなく、設計上のものです。最も有名なのはモンキーパッチです。Railsで提供されるナイフはRubyで提供されるものほど鋭くはありませんが、それでも切れ味は十分です。私は、すべてのプログラマには、権利とまではいかないまでも、完全に有能なRubyとRailsのプログラマになる道があると信じています。そして、有能というのは、状況に応じて、いつ、どのように、引き出しの中のさまざまな、時に危険なツールを使うべきかを知ることができる知識を持っているという意味です。

7. 統合システムを尊重する

Railsは様々な文脈で利用できますが、一番は統合システムを作ることです。 壮大なモノリス!問題全体に対処するシステム全体です。つまり、RailsはフロントエンドのJavaScriptから、DBまで、すべてに関わっているということです。これは非常に広い範囲ですが、一人の人間が理解するのに非現実的ほど広い範囲ではありません。Railsは特に、ジェネラリストの個人がこれらの完全なシステムを作れるようにすることを目指しています。個人に力を与えることに焦点を当てているのが、統合されたシステムなのです。1つの統合システムとしての使いやすさと分かりやすさを備え、個別にチューニングされ分散されたアプリケーションの力を得ることができます

  • コメント

あまりマイクロサービスには賛成じゃなさそうですね

8.安定性より進歩

Railsのようにシステムが10年以上前から存在している場合、自然と固定化していく傾向にあります。過去の挙動に依存していた人にとって、どんな変更でも問題になる可能性があるのは当然です。しかし、あまりにも保守的になっていると、反対側に何があるか見えなくなります。進化と成長のためには現状を壊し変更する必要があります。進化することで、これからの数十年Railsが生存と繁栄をすることができます。

Railsのバージョンも上がっていっていますが、Rubyの新しいバージョンを素早く採用することで、Rubyの発展のために先頭を切って貢献するべきです。

進歩とは、結局変化を推し進めようとする人とその意欲にかかっていることがほとんどです。Rails CoreやRails Committersのようなグループに無期限の席がないのはこのためです。どちらのグループも、フレームワークの進歩に積極的に取り組んでいる人たちのためのものです。同様に、コミュニティの新しいメンバーを歓迎し、奨励し続けることが非常に重要な理由です。私たちは、より良い進歩を遂げるために、新鮮な血と新鮮なアイデアを必要としています。

9. テントを押し上げる

Railsには異論のあるアイデアがたくさん取り入れられているので、もしあらゆる考え方に対して完全に従うよう常に皆さんに求めていたら、Railsは思想的な隠者が集まるグループとしてすぐに孤立してしまうでしょう。ですので、それはありません!意見の相違が必要です。方言が必要です。思想や人の多様性が必要なのです。このアイデアのるつぼの中にこそ最高の共有物があるのです。私がしばしば重大な不満を表明してきたテスト用DSLであるRSpecの継続的な成功は、完璧な証拠です。私はなぜこれではいけないと思うのか、顔が真っ青になるまでわめき散らすことができますが、それでもRSpecはまだ花開き、繁栄することができます。その点の方が遥かに重要なのです

APIとしてのRailsの登場についても同じことが言えます。私の個人的な焦点とこだわりは、ビューを含む統合システムにありますが、クライアントやサーバを先行して配布したいと考えている人には、Railsがうまくいく余地があることは間違いありません。

大きなテントを持つということは、すべての人に万能であろうとすることではありません。すべての人を歓迎し、自分の飲み物を持ってくることを許可するということだ。他の人にも参加してもらうことで、私たちの魂や価値観を失う必要はないし、新しいおいしい飲み物の混ぜ方も学べるかもしれない。これはタダではできません。歓迎するためには努力が必要です。特に、あなたの目標が、コミュニティの既存メンバーと同じような人をさらに集めることだけが目的でない場合は、なおさらです。参入障壁を下げることは、常に真剣に取り組むべき仕事です。ドキュメントのスペルミスを修正するだけで始めた次の人が、いつ次の素晴らしい機能を実装することになるかわかりません。しかし、あなたが微笑んで、どんな小さな貢献にも感謝することで、モチベーションを高めることができるかもしれません。

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

100日後に1人前になる新人エンジニア(5日目)

100日後に1人前になる新人エンジニア(5日目)

100日後に1人前になる新人エンジニア(4日目)

どうもこんばんは早くも5日目になりました。
今日のお題はRailsのroutesに関してです。
調べてみると結構いろんなルールがありましたのでまとめてみたいと思います。

Rails のルーティング

Railsルーター

Railsのルーターは受け取ったURLを認識し、適切なコントローラ内アクションに割り当てるよ。

GET /patients/17                  

こんなURLでリクエストがあったとき

get '/patients/:id', to: 'patients#show' 

patientsコントローラのshowアクションに割り当てられられています。
17の部分は :idに対応しているよ。
to: 以降は 'コントローラー#アクション' って感じで対応付けがされています。

ResourceベースのRouting

関連するいろんなリクエストを1つのアクションにまとめたやつです。

resources :users

こんな感じでroutesに一行書くだけで以下のrouteが追加されます

HTTP パス コントローラ#アクション
GET /users users#index
GET /users/new users#new
POST /users users#create
GET /users/:id users#show
GET /users/:id/edit users#edit
PATCH/PUT /uses/:id users#update
DELETE /users/:id users#destroy

コントローラの名前空間とrouting

名前空間によってグループ化することができるよ。

namespace :admin do
  resources :users
end
HTTP パス コントローラ#アクション
GET /admin/users admin/users#index
GET /admin/users/new admin/users#new
POST /admin/users admin/users#create
GET /admin/users/:id admin/users#show
GET /admin/users/:id/edit admin/users#edit
PATCH/PUT /admin/uses/:id admin/users#update
DELETE /admin/users/:id admin/users#destroy

こうすることで/adminから始まるグループでルーティングを作成することができるんだね。

ちなみにresourcesでいくつかのroutesを選択して使用したい場合は

resources :articles do
  resources :comments, only: [:index, :new, :create]
end

こんな感じで only:以降に使用したいroutesを指定すれば良い。
これで必要のないroutesを指定することもなさそうだね。

名前付きrouting

:asオプションを使うと、どんなルーティングにも名前を指定できます。

get 'exit', to: 'sessions#destroy', as: :logout

上のルーティングではlogout_pathとlogout_urlがアプリケーションの名前付きルーティングヘルパーとして作成されます。logout_pathを呼び出すと/exitが返されます。
という様に:asで名前付きroutingを作成できます。

resourcesの時にも名前付きroutingは作成されています。

基本的にはurlの指定は名前付きrouteで指定することが多そうなので、
この方法に慣れることは結構重要だなと最近感じております。

全部書くと無限にありそうなので今日はここら辺でおしまい。
わからなくなったらRailsガイドをみて都度確認する癖をつけたい。

1人前のエンジニアになるまであと95日

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

ユーザー登録時のエラーメッセージ、サクセスメッセージの表示の仕方

はじめに

RailsでWebサービスを作成の際にサインんアップ時のフラッシュメッセージの表示の仕方を自分の得た知識の整理のために記事を作成しました。
基本はRailsチュートリアルで得た知識になります。

前提条件

  • Railsを使用したWebサービスを作成途中で、すでに基本的なログイン機能を自分でdeviceを使用せず作ったとします。
  • バリデーション実装済み

controller

def create
    @user = User.new(user_params) 
    if @user.save   #@userの保存に成功
      redirect_to @user  #ユーザー詳細にリダイレクト
    else
      render 'new'  #失敗時は新規登録へ戻る
    end
  end

  private

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

新規登録に失敗したときのエラーメッセージ

新規登録の際にパスワードが空白などのバリデーションに引っかかった場合に新規登録画面に戻ってエラーメッセージを表示します。

new.html.erb
 <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>

上記のコードではform_forメソッドの中にrenderを挿入しsharedフォルダにまとめてあるerror_messagesのファイを持ってきています。
エラーメッセージなどを挿入する際には、個別のviewにコードを記述しても結果としては変わりません。しかし、複数のviewで使用されるパーシャルはsharedディレクトリを作成してそこで管理し、そして必要なところにrenderで挿入するのがRailsでは慣習となっています。

では、error_messagesのファイルをみてみましょう。

_error_messages.html.erb
<% if @user.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    <ul>
    <% @user.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

このコードでは様々なメソッドが使用されています。一つずつみていきましょう。

<% if @user.errors.any? %>

この行ですが@user.errorsに対してany?メソッドを使用しています。
any?メソッドは読んで字の如く、指定した対象一つでもあれば、論理値でtrueを返します。
そのため、ここはif文の条件式で「@userになんか不備ある?」ということを聞いています。
そして、何か不備があった場合はその下の処理が発動して、先ほどのnew.html.erbに挿入されます。

The form contains <%= pluralize(@user.errors.count, "error") %>.

次にこのコードです。
これはエラーの数を数えて表示しますというコードです。
ここで見るからに難しそうな単語のpluralizeというメソッドがありますが全然難しくありません。
これは勝手に数えた数に応じて単数形と複数形をわけて出力してくれるメソッド(英語限定)です。
helperオブジェクトを使用して例にあげると

>> helper.pluralize(1, "error")
=> "1 error"
>> helper.pluralize(5, "error")
=> "5 errors"

上記ではhelperオブジェクトに対して使用したpluralizeメソッドが第一引数で整数値をとった場合、その整数値に合わせて第二引数の英単語を複数形にして出力します。
このメソッドのすごい所は可算名詞だけでなく不可算名詞にも対応しているので何かを数えて表示したい際に、不自然な形になることを防げます。
改めて今回のコードをみます。

The form contains <%= pluralize(@user.errors.count, "error") %>.

pluralizeメソッドの第一引数には@user.errors.countがあります。ここではcountメソッドで@user.errorsの数を数えます。そして、第二引数で指定した単位で単数形、複数形判断して出力します。
結果、The form contains 3 errorsとかの表記になります。

<% @user.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>

そして、このeach文で実際に起きているエラーの内容を表示します。

<% @user.errors.full_messages.each do |msg| %>

この文の中のerrors.full_messagesという部分はオブジェクトに対してエラーがないか調べ、もしあった場合は何が悪いのか元々Railsに備え付けられているエラーメッセージを使用して出力します。(日本語で出力するやり方もあるのですが調べてません。)
そして、その出力したエラーメッセージをmsgというブロックに入れて<li><%= msg %></li>で出力します。

新規登録成功時のサクセスメッセージ

サクセスメッセージを表示する際はコントローラーを少し弄ります。

users_controller.rb
def create
    @user = User.new(user_params)
    if @user.save
      flash[:success] = "Welcome to the Sample App!" #成功したときのメッセージの指定
      redirect_to @user
    else
      render 'new'
    end
  end

flash

サクセスメッセージはページにリダイレクトした直後だけ表示するようにします。そのためにはflashという特殊な変数を使用します。そして上記のコントローラーではそのflash変数のキーがsuccess(Railsの慣習で成功時を表す)の時は"Welcome to the Sample App!"をバリューとして返すように設定しています。
そしてapplication.html.erbでflash変数にeachメソッドを使用してサクセスメッセージを表示します。
追加するコードは下記の通り、

applocation.html.erb
<!DOCTYPE html>
<html>
.
.
.
<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
.
.
.
</body>
</html>

上記のコードはちょっとややこしい上に周りくどい書き方をしているのであとでさらに簡略化します。

<% flash.each do |message_type, message| %>

この時点で、flash変数の中にはsuccessというキーと"Welcome to the Sample App!"というバリューがあるのでeachメソッドで取り出してmessage_typeというキーのブロックとmessageというバリューのブロックに入れます。
そして下記で出力します。

<div class="alert alert-<%= message_type %>"><%= message %></div>

class="alert alert-<%= message_type %>"というBootstrapで使用するクラスがありますがここに先ほどのキーを入れるとclass="alert alert-success"となりBootstrapがカッコよくしてくれます。

これでも正常に表示はされるのですが読みにくいのでcontent_tagメソッドを使用して一行にします。

application.html.erb
<!DOCTYPE html>
<html>
.
.
.
<% flash.each do |message_type, message| %>
     <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
<% end %>
.
.
.
</body>
</html>

こっちの方が読みやすいですね!

感想

基本的なサクセス、エラーメッセージの付け方は理解できたので、エラーメッセージを日本語化したり、deviseを使用した時の方法(個人的にこっちの方が重要なんじゃないかとこの記事書きながらずっと考えてたw)についてもしっかり理解していきたいと思います。

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

ハードコーディングとは

はじめに

この業界で仕事をし始めてから日々聞きなれない単語を聞くことが多いのですが、今日は「ハードコーディング」という言葉を耳にして意味がわからなかったので、調べた結果をアウトプットさせていただきます。

ハードコーディングとは

主にこちらの記事を参考にしました。
「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

記事にも書いてありますが、一言で言えば、「別のところに分けておいた方が良い処理や値をソースコードの中に直接埋め込むこと」です。

具体的なプログラムで見るとわかりやすいです。

sample.rb
price = gets
puts price + price * 0.1

上は物の値段を標準入力で受け取って、消費税率10%の税込価格を表示するごく簡単なRubyスクリプトです。
プログラムでは消費税率0.1が、税込価格を計算する箇所に直接書かれています。このスクリプトを書いた本人は税込価格を表示するプログラムだとわかっているため特に問題ありませんが、他の人が見たときに0.1って何?、みたいな感じでプログラムの意味が他人には把握しづらいです。
このように分けておいたほうがわかりやすい処理や値(今回は税率)を直接コードに書くことをハードコーディングと言います。
さらにハードコーディングされているのが数字の場合、それらは「マジックナンバー」と呼ばれます。
今回の税率0.1はマジックナンバーとなります。

複数人で開発する際にハードコーディングがあると、他の人がコードを見たときに処理がわかりづらいため、一般には避けるべき書き方です。

今回の税込価格プログラムのハードコーディングを解消すると、例として以下のようになります。

sample.rb
price = gets
tax = 0.1
puts price + price * tax

消費税をtaxとして分離することで、可読性が上がるとともに、将来消費税が変更になってもtax部分だけを変更すればいいので、修正も楽になります。
ハードコーディングとは逆に処理を分離する書き方は、ソフトコーディングと呼ばれるそうです。

今回は税込価格表示の簡単なプログラムだったためハードコーディング解消のありがたみがわかりづらいですが、巨大なプログラムになるとソフトコーディングによる恩恵は大きいと思われます。
具体的にはデータベース設定などはハードコーディングを避けるべき項目だと考えられます。

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

【Rails 6】active_strageの画像の保存タイミングが変更された

今回はRails6から画像の保存のタイミングが変更したようなので調べてみました。

Rails バージョンの確認

$ rails -v
Rails 6.0.2.1

ActiveStrageのインストール

$ rails active_storage:install
$ rails db:migrate

画像添付の準備

最終成果物を載せます。

routeにpreviewを追加します。

route.rb
Rails.application.routes.draw do
  resources :users do
    collection do
      post :preview
      patch :preview
    end
  end
end

モデルにhas_one_attachedで画像を扱えるようにします。

user.rb
class User < ApplicationRecord
  has_one_attached :image
end

次にcontrollerです。
今回はプレビュー機能をつけたいのでpreviewアクションを追加。

user_controller.rb
class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    @users = User.all
  end

  # GET /users/1
  # GET /users/1.json
  def show
  end

  # GET /users/new
  def new
    @user = User.new
  end

  # GET /users/1/edit
  def edit
  end
.
.
.
.
  def preview
    @user = User.new(user_params)
    image_binary = ''
    if @user.image.attached?
      image_binary = @user.attachment_changes['image'].attachable.read
    else
      saved_user = User.find_by(id: params[:id])
      if saved_user.present? && saved_user.image.attached?
        image_binary = saved_user.image.blob.download
      end
    end
    @image_enconded_by_base64 = Base64.strict_encode64(image_binary)

    render template: 'users/_preview', layout: 'application'
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def user_params
      params.require(:user).permit(:name, :image)
    end
end

最後にhtmlです。

new.html.erb
<h1>New User</h1>

<%= render 'form', user: @user %>

<%= link_to 'Back', users_path %>
_form.html.erb
<%= form_with(model: user, local: true) do |form| %>
  <% if user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
        <% user.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
    <%= form.label :image %>
    <%= form.file_field :image %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @user.name %>
  <% if @user.image.attached? %>
    <%= image_tag @user.image %>
  <% end %>
</p>

<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>
_preview.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @user.name %>
  <% if action_name == 'preview' %>
    <img src="data:image/png;base64,<%= @image_enconded_by_base64 %>" />
  <% else %>
    <%= image_tag @member.image %>
  <% end %>
</p>

これで準備ができました。

Rails6での画像の保存のタイミング

早速画像をアップロードしていきます。

スクリーンショット 2020-05-21 18.04.06.png

テスト.jpegをアップロードしてCreate Userボタンを押すと

スクリーンショット 2020-05-21 18.18.57.png

createは成功しています。
ここでアップロードした画像を見に行くと

irb(main):005:0> @user.image.attachment
  ActiveStorage::Attachment Load (1.5ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 1], ["record_type", "User"], ["name", "image"], ["LIMIT", 1]]
=> #<ActiveStorage::Attachment id: 1, name: "image", record_type: "User", record_id: 1, blob_id: 1, created_at: "2020-05-21 09:04:12">

画像が存在するのがわかりました。

アップロードでは保存できていない

次に新しくpreviewを試みます。

スクリーンショット 2020-05-21 18.20.20.png

同じようにプレビュー.jpgをアップロードしてpreviewボタンを押して画像の確認をします。

すると

> @user.image.attachment
NameError: uninitialized constant #<Class:0x00007fd450065780>::Analyzable
from /usr/local/bundle/gems/activestorage-6.0.3.1/app/models/active_storage/blob.rb:26:in `<class:Blob>'

ない!!

> params[:user][:image]
=> #<ActionDispatch::Http::UploadedFile:0x000055892ec2b598
 @content_type="image/jpeg",
 @headers=
  "Content-Disposition: form-data; name=\"user[image]\"; filename=\"\xE3\x83\x95\xE3\x82\x9A\xE3\x83\xAC\xE3\x83\x92\xE3\x82\x99\xE3\x83\xA5\xE3\x83\xBC.jpg\"\r\nContent-Type: image/jpeg\r\n",
 @original_filename="プレビュー.jpg",
 @tempfile=#<File:/tmp/RackMultipart20200522-1-ha9tz1.jpg>>

パラメータを確認。
画像をアップロードしただけでは保存されないのか!

下記を参考にすると
https://github.com/rails/rails/pull/33303

rails5では

@user.image = params[:image]

attributeに入れたタイミングですね!

まさかrails6になってこんな変更があるなんて!!
このままだとプレビュー時(画像をインスタンスに保存せず)に画像をViewに渡せない。

どうにかいい方法が無いか調べていると

https://github.com/rails/rails/pull/33303

https://stackoverflow.com/questions/57564796/reading-from-active-storage-attachment-before-save

あった!!

record.attachment_changes['<attributename>'].attachable

試す!

> @user.attachment_changes['image'].attachable
=> #<ActionDispatch::Http::UploadedFile:0x000055892ec2b598
 @content_type="image/jpeg",
 @headers=
  "Content-Disposition: form-data; name=\"user[image]\"; filename=\"\xE3\x83\x95\xE3\x82\x9A\xE3\x83\xAC\xE3\x83\x92\xE3\x82\x99\xE3\x83\xA5\xE3\x83\xBC.jpg\"\r\nContent-Type: image/jpeg\r\n",
 @original_filename="プレビュー.jpg",
 @tempfile=#<File:/tmp/RackMultipart20200522-1-ha9tz1.jpg>>

さらに

@user.attachment_changes['image'].attachable.read

↑でバイナリデータを取ってくることができたのでBase64でエンコードして渡すと

@image_enconded_by_base64 = Base64.strict_encode64(@user.attachment_changes['image'].attachable.read)

スクリーンショット 2020-05-22 10.48.00.png

できました!!

まとめ

ActiveStrageの保存のタイミングに変更があるなんて知りませんでした。
保存せずに画像を触りたいなら

record.attachment_changes['<attributename>'].attachable.read

でバイナリ読み出してBase64!!(Base64でなくてもいいけど)

他に何かいい方法あれば教えてください。

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

nginx 起動コマンド

nginx設定箇所

/etc/nginx/conf.d

nginxコマンド

  • 起動
sudo service nginx start
  • 再起動
sudo nginx -s reload
  • 停止
sudo nginx -s stop
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

『kaminari』配列に対してページネーションを作成する方法

はじめに

今回は、配列に対してページネーションを作成しビューで表示するまでのサンプルコードを紹介します。

コントローラー

お気に入りした商品をユーザーのマイページにて表示するためにコントローラーでこのようなコードを書きました。実はこれ、アソシエーション組めばもっと簡単にできるので以下の記事を参考にしてみてください!
https://qiita.com/yummy888/items/22db2f8b79b5be148b69

 def index
    items = []
    likes = Like.where(user_id: params[:item_id])
    if likes.present?
      likes.each { |like| items << Item.find(like.item_id)}
    end
    @items = Kaminari.paginate_array(items).page(params[:page]).per(15)
  end

user_idになんでparams[:item_id]渡してるの?と思ったかもしれませんが、root.rbにてitemsにlikesをネストしているため、このようなパラメーターの渡し方になっています。(パラメーターでは:item_idとなっているところに,current_userをpathで渡している)本当だったらusesにitemsをネストして、さらにlikesをネストすればもっと綺麗なパラメーターの渡し方ができると思います。

ビュー

hamlでやってます。erbだったらeach文のendの真下にページネーションおく感じですね

 - @items.each do |item|
   = link_to item_path(item), class: "user__liked__items" do
     .user__liked__items__image
       = image_tag "#{item.images[0].image.url}", height:"50px", width: "50px", class: "user__liked__item__image"
       -if item.buyer_id.present? 
         .items__image__sold
           .items__image__sold__inner
              SOLD
       .user__liked__item__details
         .user__liked__item__name
           = item.name
         .user__liked__item__price
           %span
             ¥
             = item.price.to_s(:delimited)
           %i.fas.fa-chevron-right
  = paginate @items

まとめ

自分で色々実装できるようになると、より簡単な実装方法の模索を放棄してしまってると感じるので、そこは改善していきたいですね、、、ちょっとできるように、なっただけで付け上がっちゃいかんのう:santa:

でも、ページネーション作成する場合だったらどちらにしてもコントローラーで色々やらないといけないのかな??ビューでparams[:page]とかできないよな?多分

次この機能実装するときはまた違った実装の方法を試してみますか:frowning2:

参考記事

https://blog.konboi.com/post/2013/03/31/224939/

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

PAY.JPで登録したクレジットカードで商品購入機能を実装する

はじめに

個人アプリにて、クレジットカード決済を行うため、PAY.JPを導入しました。
機能実装に関して、備忘録として記載しています。

前回の記事
PAY.JPでクレジットカードの登録・削除機能を実装する

前提条件

  • Rails 5.2.4.2
  • Ruby 2.5.1
  • devise使用
  • haml使用
  • VSCode使用

前回までにカード情報の登録を行うためCardモデル及びCardコントローラーを作成しました。
今回は新たに購入用モデル及びコントローラーを作成して購入処理を行っていきます。

手順

  1. モデルの作成
  2. テーブルの作成
  3. コントローラーの作成

モデルの作成

今回はOrderモデルを作成していきます。

class Order < ApplicationRecord
  belongs_to :user
  has_many :products, through: :order_details
  has_many :order_details, dependent: :destroy
  belongs_to :card
  belongs_to :address

  enum postage: {burden: 0, free: 1}
  enum status: {支払済み: 0, 配送準備中: 1, 配送済み: 2}

  def add_items(cart)
    cart.line_items.target.each do |item|
      item.cart_id = nil
      line_items << item
    end
  end
end

ECサイトを作成したので、カート機能などを実装している関係で、インスタンスメソッドなど定義しておりますが、今回は購入機能にフォーカスしているため、割愛します。
なので、belongs_to :cardの1行が使いたい部分になります。

テーブルの作成

マイグレーションファイルを以下のようにします。

class CreateOrders < ActiveRecord::Migration[5.2]
  def change
    create_table :orders do |t|
      t.references :user, foreign_key: true
      t.references :address, foreign_key: true
      t.references :card, foreign_key: true
      t.references :product, foreign_key: true
      t.integer :quantity, null: false
      t.integer :status, default: 0, null: false
      t.integer :postage, default: 0, null: false
      t.integer :price, null: false
      t.timestamps
    end
  end
end

cardを外部キーとして設置しています。

コントローラーの作成

Orderコントローラーを作成していきます。
今回はnewとcreateの2つのアクションを使用します。

class OrdersController < ApplicationController
  before_action :set_cart
  before_action :user_signed_in
  before_action :set_user
  before_action :set_card
  before_action :set_address

  require "payjp"

  #注文入力画面
  def new
    @line_items = current_cart.line_items
    @cart = current_cart
    if @cart.line_items.empty?
      redirect_to current_cart, notice: "カートは空です"
      return
    end
    if @card.present?
      customer = Payjp::Customer.retrieve(@card.customer_id)
      default_card_information = customer.cards.retrieve(@card.card_id)
      @card_info = customer.cards.retrieve(@card.card_id)
      @exp_month = default_card_information.exp_month.to_s
      @exp_year = default_card_information.exp_year.to_s.slice(2,3)
      customer_card = customer.cards.retrieve(@card.card_id)
      @card_brand = customer_card.brand
      case @card_brand
      when "Visa"
        @card_src = "icon_visa.png"
      when "JCB"
        @card_src = "icon_jcb.png"
      when "MasterCard"
        @card_src = "icon_mastercard.png"
      when "American Express"
        @card_src = "icon_amex.png"
      when "Diners Club"
        @card_src = "icon_diners.png"
      when "Discover"
        @card_src = "icon_discover.png"
      end
      @order = Order.new
    end
  end

  #注文の登録
  def create
    unless user_signed_in?
      redirect_to cart_path(@current_cart), notice: "ログインしてください"
      return
    end
    @purchaseByCard = Payjp::Charge.create(
    amount: @cart.total_price,
    customer: @card.customer_id,
    currency: 'jpy',
    card: params['payjpToken']
    )
    @order = Order.new(order_params)
    @order.add_items(current_cart)
    if @purchaseByCard.save && @order.save!
      OrderDetail.create_items(@order, @cart.line_items)
      flash[:notice] = '注文が完了しました。マイページにて注文履歴の確認ができます。'
      redirect_to root_path
    else
      flash[:alert] = "注文の登録ができませんでした"
      redirect_to action: :new
    end
  end

  private
  def order_params
    params.permit(:user_id, :address_id, :card_id, :quantity, :price)
  end

  def set_user
    @user = current_user
  end

  def set_cart
    @cart = current_cart
  end

  def set_card
    @card = Card.find_by(user_id: current_user.id)
  end

  def set_address
    @address = Address.find_by(user_id: current_user.id)
  end

  def user_signed_in
    unless user_signed_in?
      redirect_to cart_path(@cart.id), alert: "レジに進むにはログインが必要です"
    end
  end
end

newアクションにおいては、購入確認ページの形態をとっています。
ユーザーが登録していないと、そもそも購入確認ページに訪問できない仕様になっております。
かつ、ユーザーがマイページでカードを登録できるように仕様です。

購入確認ページで初めてカード登録を行う仕様にするのであれば、前回の記事で作成したCardコントローラーのnewアクションのインスタンス変数を持ってこればいいかもですね。

createアクションでは以下の内容が必要です。
見易いように内容抽出しております。

def create
  @purchaseByCard = Payjp::Charge.create(
  amount: @cart.total_price,
  customer: @card.customer_id,
  currency: 'jpy',
  card: params['payjpToken']
  )
  if @purchaseByCard.save
    flash[:notice] = '注文が完了しました。'
    redirect_to root_path
  else
    flash[:alert] = "注文の登録ができませんでした"
    redirect_to action: :new
  end
end

上記で購入が完了すると以下のようにPAY.JPにも売上が登録されています。
alt

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

Ajax通信をrubyに導入した話

jQueryでAjax通信 × Railsでサーバー処理

jQueryのコードを使ってAjax通信でリクエストし、その処理をRailsで行ってjson形式のレスポンスで返したいと思います。
具体的には、「ボタンをクリックしたらリクエストが発動して、設定したデータをRailsに送信し、レスポンスをクラインアントが受け取っとら、アラートやコンソール上で送られてきたデータを表示する」ということをやってみたいと思います。

今回は、クライアント側とサーバー側を分かりやすくするため、Erbを使わずに処理を行いたいと思います。
したがって、今回利用するファイルの拡張子は以下になります。

クライアント側

  • html
  • js

サーバー側

  • rb

HTMLファイルで必要なコード

・jQueryのサーバーから、jQueryを使えるようにする機能を読み込むコード(いわゆるjQueryの導入)

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

・JavaScriptファイルを読み込むコード

<script src="sample.js"></script>

・サーバーへリクエストするボタン

<input type="button" id="btn1" value="ボタン">

これらを組み合わせたhtmlのソースコードは以下になります。

sample.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Ajaxの練習</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script src="sample.js"></script>
</head>
<body>
  <input type="button" id="btn1" value="ボタン">
</body>
</html>

JavaScriptファイルに必要なコード

Ajax通信するコード

以下のような違いがあるため、今回は$.ajaxを使うことにします。

$.ajax 通信の成功時も失敗時の処理も書ける
$.post 通信の成功時の処理のみしか書けない

パラメータ

・クライアントからのリクエストの送信先を指定

url: "/test"

・HTTP通信の種類を設定
GETとPOSTがありますが、今回はPOSTで、データを送信します。

type: "POST"

・サーバーからのレスポンスを受け取るデータ形式を設定。

dataType: "json"

・サーバーへ送るデータの中身

data: { user: { name: "foo", age: 25 } }

メソッド

・成功時の処理

通信が成功したか分かりやすくするため、アラートでデータを表示されるようにしています。
GoogleChromeだと、alertだけでは「object」としか表示されないため、
特別なメソッド(JSON.stringify)を追加する必要がある。

done(function(human){
  alert(JSON.stringify(human));
});

・失敗時の処理(任意)

通信が失敗したら、送ったデータをコンソール上で表示するようにしています。responseTextメソッドは、Ajax通信途中のデータを取得できるメソッド。

fail(function(human){
  console.log(human.responseText);
});

・成功しても失敗しても行われる処理

always(function(){
  console.log(human);
});

これらのjQueryのコードをまとめると以下になります。

補足
id属性btn1のボタンをクリックしたら、以下のAjax通信を行う($.ajax)
・/testに、userというデータをPOSTで送る。
・サーバーからのレスポンス時にはJSON形式でデータを受け取る。
・通信成功時には、返ってきたデータ(変数human)をアラートでブラウザに表示する。(.done)
・通信失敗時には、レスポンスされたデータを文字列としてコンソール上に表示する。(.fail)
・通信成功時でも失敗時でも、レスポンスされたデータをコンソール上に表示する。(.always)

sample.js
$(function(){
  $('#btn1').click(function(){
    $.ajax({
      url: "/test",
      type: "POST",
      dataType: "json",
      data: { user: { name: "foo", age: 25 } }
    })
    .done((human) => {
      alert(JSON.stringify(human))
    })
    .fail((human) => {
      console.log(human.responseText)
    })
    .always((human) => {
      console.log(human)
    });
  });
});

Rails(サーバー側)で必要なコード

・ルーティング
jsファイルに記述した、リクエストの送信先(/test)を設定します。

route.rb
Rails.application.routes.draw do
  get "/test", to: 'statics#test'
  post "/test", to: 'statics#test'
end

・コントローラ

jsファイルに記述した、送信するデータを受け取り、json形式でクライアントにレスポンスで返します。
・送信されたデータ{ user: { name: "foo", age: 25 } }を、変数humanで受け取る
・変数humanをjson形式でレスポンスする。

statics_controller.rb
class StaticsController < ApplicationController
  def test
    human = params[:user]
    render :json => human
  end
end

最後に

うまくいけば以下のような表示になると思います。
スクリーンショット 2020-05-25 16.18.45.png

いかがだったでしょうか、非同期通信を行うことで身軽な通信ができて、大勢の人に知れ渡るアプリへの一歩になるんじゃないかと思います。

ただその分、初心者には複雑な仕組みになりがちなので、参考にしていただければと思います。

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

Ruby で解く AtCoder 第一回アルゴリズム実技検定 A 例外処理

はじめに

AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。

今回のお題

AtCoder 第一回アルゴリズム実技検定 A - 2 倍チェック
Difficulty: ???

今回のテーマ、例外処理

基本に忠実であれば正規表現で解答しますが、Ruby をより深く理解するために例外処理で解答したいと思います。

Ruby 正規表現

3文字とも数字だったらのパターン

ruby1.rb
s = gets.chomp
puts (/[0-9]{3}/ === s ? s.to_i * 2 : 'error')

s = gets.chomp
puts (s.match(/[0-9]{3}/) ? s.to_i * 2 : 'error')

1文字でも英小文字だったらのパターン

ruby2.rb
s = gets.chomp
puts (/[a-z]/ === s ? 'error' : s.to_i * 2)

s = gets.chomp
puts (s.match(/[a-z]/) ? 'error' : s.to_i * 2)

1文字でも数字以外だったらのパターン

ruby3.rb
s = gets.chomp
puts (/[^0-9]/ === s ? 'error' : s.to_i * 2)

s = gets.chomp
puts (s.match(/[^0-9]/) ? 'error' : s.to_i * 2)

Ruby to_i (WA)

to_iを直接適用したパターン

ruby4.rb
s = 'abc'
puts s.to_i # => 0
s = '42A'
puts s.to_i # => 42

リファレンス整数とみなせない文字があればそこまでを変換対象とします。とありますので、ここまでです。

Ruby Rational (AC)

Rationalを適用したパターン

ruby5.rb
s = gets.chomp
begin
  Rational(s)
rescue
  puts 'error'
else
  puts s.to_i * 2
end

例外処理の制御構造は次の通りです。

rescue.rb
begin
  # 例外が発生する可能性のある処理
[rescue [error_type,..] [=> evar] [then]
  # 例外が発生した時の処理
[else
  # 例外が発生しなかった時の処理]
[ensure
  # 例外に関係なく実行される処理]
end

begin else ensure は省略可能です。

普段はあまり使用しないRationalですが、「1/3」のような有理数を扱うクラスです。
to_rですと先頭の数字を解釈しますが、Rationalですと上手く例外が発生するようです。

ruby6.rb
puts '0x8'.to_r     # => 0/1
puts Integer('0x8') # => 8
puts Float('0x8')   # => 8.0
puts Complex('0x8') # => ArgumentError
puts Complex('08i') # => 0+8i

Complexは複素数を扱うクラスで、0x8では例外が発生するのですがiを含む文字列はすり抜ける可能性があります。

標準出力する際にto_iを使用するのが心苦しいと申しますか、やはり普通に正規表現を使用するのがいいと思います。

まとめ

  • 第一回アルゴリズム実技検定 A を解いた
  • Ruby に詳しくなった
  • 素直に正規表現を使用しましょう

参照したサイト
AtCoderでRuby学習8【第一回アルゴリズム実技検定 2倍チェック 】正規表現
instance method String#to_i
class Rational
制御構造

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

PAY.JPでクレジットカードの登録・削除機能を実装する

はじめに

個人アプリにて、クレジットカード決済を行うため、PAY.JPを導入しました。
導入において、少しつまずいた部分もあったので、備忘録として記載しています。

前提条件

  • Rails 5.2.4.2
  • Ruby 2.5.1
  • devise使用
  • haml使用
  • VSCode使用

手順

  1. PAY.JPの登録
  2. アプリにPAY.JPを導入・下準備
  3. モデルの作成
  4. マイグレーションファイル
  5. コントローラーの作成
  6. gonの導入
  7. JSファイルの作成

PAY.JPの登録

以下のURLより登録します。
PAY.JP

41b709e395800ed89184beb6bbfee74f.png

登録が完了すると、上記のような画面に移ります。(使用したため、売上がたっております。)
最初はテストモードになっております。
実取引を行うためには,申請を行い、ライブモードに切り替える必要があります。
今回は個人アプリでの使用であり、商用目的ではないので、テストモードを使用しています。

メニューバーにある"API"の内容を使います。
996ed64b5c8b7f2c66a991720dc78f83.png

自身のアプリにはこのテスト秘密鍵とテスト公開鍵を使用していきます。

アプリにPAY.JPを導入・下準備

Gemfileに以下を追記

gem 'payjp'

ここから、カード情報の入力フォームを作成準備をしていくのですが、やり方としては2つあります。

カード情報のトークン化について

  1. チェックアウト
  2. カスタム

チェックアウトでは

<form action="/pay" method="post">
  <script src="https://checkout.pay.jp/" class="payjp-button" data-key=""></script>
</form>

上記を記述することでPAYJP側が用意したフォームを使用できます。

今回は自分はフォームを自身でカスタムしたかったので、以下のようにやっていきました。

application.html.hamlのhead部分に以下を追記します。

 %script{src: "https://js.pay.jp", type: "text/javascript"}

公式では上記に加えて、公開鍵の記述もしておりますが、自分は別で記載しました(後述)。

続いて、PAYJPの公開鍵と秘密鍵をcredentials.yml.encに記述していきます。

※Rails5.2系から導入されたEncrypted Credentialsという機能を利用します。これは、APIキーなどセキュリティ的に外部に公開してはいけない値の管理をシンプルに行うことができる機能です。

alt

当該ファイルを編集するには少し準備が必要です。
まずは、ターミナルからVSCodeを起動できるよう設定を行います。
VSCodeで、「Command + Shift + P」を同時に押してコマンドパレットを開きます。
続いて、「shell」と入力しましょう。
メニューに、「PATH内に'code'コマンドをインストールします」という項目が表示されるので、それをクリックします。
この操作を行うことで、ターミナルから「code」と打つことでVSCodeを起動できるようになりました。

続いて以下にてファイルの中身を編集していきます。

$ pwd
# 自身のアプリディレクトリにいることを確認
$ EDITOR='code --wait' rails credentials:edit

しばらくすると、以下のようにファイルが開きます。
alt

ここに以下のように記述していきます。
ネストしていることに注意してください。

payjp:
  PAYJP_SECRET_KEY: sk_test_...
  PAYJP_PUBLIC_KEY: pk_test_...

ご自身の鍵を記述して、保存してからファイルを閉じると
”New credentials encrypted and saved.”
とターミナルに出力されるので完了です。

なお、保存できているかどうかは以下コマンドで確認できます。

$ rails c
$ Rails.application.credentials[:payjp][:PAYJP_SECRET_KEY]
$ Rails.application.credentials[:payjp][:PAYJP_PUBLIC_KEY]

モデルの作成

今回はCardモデルと命名してやっていきます。
以下のように作成しました。

class Card < ApplicationRecord
  belongs_to :user
  has_one :order, dependent: :nullify

  require 'payjp'
  Payjp.api_key = Rails.application.credentials.dig(:payjp, :PAYJP_SECRET_KEY)

  def self.create_card_to_payjp(params)
    # トークンを作成 
    token = Payjp::Token.create({
      card: {
        number:     params['number'],
        cvc:        params['cvc'],
        exp_month:  params['valid_month'],
        exp_year:   params['valid_year']
      }},
      {'X-Payjp-Direct-Token-Generate': 'true'} 
    )
    # 上記で作成したトークンをもとに顧客情報を作成
    Payjp::Customer.create(card: token.id)
  end

個人アプリではECサイトを作成していたため、アソシエーションはUserとOrderとしています。

ここでカード情報のトークン化を行っております。

テーブルの作成

以下の内容でテーブルを作成しました。

class CreateCards < ActiveRecord::Migration[5.2]
  def change
    create_table :cards do |t|
      t.string :customer_id, null: false
      t.string :card_id, null: false
      t.references :user, null: false, foreign_key: true
      t.timestamps
    end
  end
end

自分は最初知らなかったですが、customer_idとcard_idはマストでカラムとして作成する必要があります。
テーブルには以下のようにレコードが登録されていきます。

コントローラーの作成

今回は4つのアクションで以下のようにコントローラーを構成しました。

class CardsController < ApplicationController
  before_action :set_card, only: [:new, :show, :destroy]
  before_action :set_payjpSecretKey, except: :new
  before_action :set_cart
  before_action :set_user

  require "payjp"

  def new
    redirect_to action: :show, id: current_user.id if @card.present?
    @card = Card.new 
    gon.payjpPublicKey = Rails.application.credentials[:payjp][:PAYJP_PUBLIC_KEY]
  end

  def create
    render action: :new if params['payjpToken'].blank?
    customer = Payjp::Customer.create(
      card: params['payjpToken']
    )
    @card = Card.new(
      card_id: customer.default_card,
      user_id: current_user.id,
      customer_id: customer.id
    )
    if @card.save
      flash[:notice] = 'クレジットカードの登録が完了しました'
      redirect_to action: :show, id: current_user.id
    else
      flash[:alert] = 'クレジットカード登録に失敗しました'
      redirect_to action: :new
    end
  end

   def show
    redirect_to action: :new if @card.blank?
    customer = Payjp::Customer.retrieve(@card.customer_id)
    default_card_information = customer.cards.retrieve(@card.card_id)
    @card_info = customer.cards.retrieve(@card.card_id)
    @exp_month = default_card_information.exp_month.to_s
    @exp_year = default_card_information.exp_year.to_s.slice(2,3)
    customer_card = customer.cards.retrieve(@card.card_id)
    @card_brand = customer_card.brand
    case @card_brand
    when "Visa"
      @card_src = "icon_visa.png"
    when "JCB"
      @card_src = "icon_jcb.png"
    when "MasterCard"
      @card_src = "icon_mastercard.png"
    when "American Express"
      @card_src = "icon_amex.png"
    when "Diners Club"
      @card_src = "icon_diners.png"
    when "Discover"
      @card_src = "icon_discover.png"
    end
  end

  def destroy
    customer = Payjp::Customer.retrieve(@card.customer_id)
    @card.destroy
    customer.delete
    flash[:notice] = 'クレジットカードが削除されました'
    redirect_to controller: :users, action: :show, id: current_user.id
  end

  private
  def set_card
    @card = Card.where(user_id: current_user.id).first
  end

  def set_payjpSecretKey
    Payjp.api_key = Rails.application.credentials[:payjp][:PAYJP_SECRET_KEY]
  end

  def set_cart
    @cart = current_cart
  end

  def set_user
    @user = current_user
  end
end

また、PAYJPの公開鍵を後述するJSファイルにベタ書きすることを避けるため、"gon"というGemを導入しました。
JSファイルでrubyの変数を使用したい時に使うという認識です。

gonの導入

Gemfileに以下を追記

gem 'gon'

application.html.hamlのhead部分に以下を追記します。

= include_gon(init: true)

これで準備はOKです。

JSファイルの作成

ここから、コントローラーのnewアクションで呼び出すnew.html.hamlと連動するJSファイルを作成します。
まずはnew.html.hamlを記載します。
※フォームに入力するカード番号等についてですが、以下の公式ページのものを使用してください。
APIで使用するテストカードはこちらです。

.cardNew
  .title 
    クレジットカード情報入力
  .cardForm
    = form_with model: @card, id: "form" do |f|
      .cardForm__number
        = f.label :カード番号, class: "cardForm__number__title"
        %span.must_check 必須
      .cardForm__field
        = f.text_field :card_number, id: "card_number", placeholder: "半角数字のみ", class: "form-group__input", maxlength: 16
      .cardForm__image
        = image_tag(image_path('cards/icon_visa.png'), class: 'visa', width: 58, height: 28)
        = image_tag(image_path('cards/icon_mastercard.png'), class: 'master', width: 47, height: 36)
        = image_tag(image_path('cards/icon_jcb.png'), class: 'jcb', width: 40, height: 30)
        = image_tag(image_path('cards/icon_amex.png'), class: 'amex', width: 40, height: 30)
        = image_tag(image_path('cards/icon_diners.png'), class: 'diners', width: 45, height: 32)
        = image_tag(image_path('cards/icon_discover.png'), class: 'discover', width: 47, height: 30)
      .cardForm__expirationdate
        .cardForm__expirationdate__details
          = f.label :有効期限
          %span.must_check 必須
          %br
        .cardForm__expirationdate__choice
          .cardForm__expirationdate__choice__month
            = f.select :expiration_month, options_for_select(["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]), {}, {id: "exp_month", name: "exp_month", type: "text"}
            = f.label :, class: "cardForm__expirationdate__choice__month__label"
          .cardForm__expirationdate__choice__year
            = f.select :expiration_year, options_for_select((2020..2030)), {}, {id: "exp_year", name: "exp_year", type: "text"}
            = f.label :, class: "cardForm__expirationdate__choice__year__label"
      .cardForm__securitycode
        .cardForm__securitycode__details
          .cardForm__securitycode__details__title 
            = f.label :セキュリティコード, class: "label"
            %span.must_check 必須
          .cardForm__securitycode__details__field
            = f.text_field :cvc,  id: "cvc", class: "cvc", placeholder: "カード背面3~4桁の番号", maxlength: "4"
          .cardForm__securitycode__details__hatena
            = link_to "カード背面の番号とは?", "#", class: "cardForm__securitycode__details__hatena__link"
      #card_token
        = f.submit "登録する", id: "token_submit", url: cards_path, method: :post

続いて、上記viewファイルに対応するJSファイルを作成します。

$(document).on('turbolinks:load', function() {
  $(function() {
    Payjp.setPublicKey(gon.payjpPublicKey);
    $("#token_submit").on('click', function(e){
      e.preventDefault();
      let card = {
          number: $('#card_number').val(),
          cvc:$('#cvc').val(),
          exp_month: $('#exp_month').val(),
          exp_year: $('#exp_year').val()
      };

      Payjp.createToken(card, function(status, response) {
        if (response.error) {
          $("#token_submit").prop('disabled', false);
          alert("カード情報が正しくありません。");
        }
        else {
          $("#card_number").removeAttr("name");
          $("#cvc").removeAttr("name");
          $("#exp_month").removeAttr("name");
          $("#exp_year").removeAttr("name");

          let token = response.id;
          $("#form").append(`<input type="hidden" name="payjpToken" value=${token}>`);
          $("#form").get(0).submit();
          alert("登録が完了しました");
        }
      });
    });
  });
});

viewファイルで登録ボタン( = f.submit "登録する", id: "token_submit")を押すと、JSが発火します。
トークン作成は Payjp.createToken というメソッドで行います。

上記までで、カードの登録までは完了です。

最後に削除機能について簡単に記載しておきます。
自分はshowアクションで呼び出すshow.html.hamlに削除ボタンを以下のように設置しました。

.payment
  .payment__content
    .payment__content__title
      カード情報
    .payment__content__box
      .payment__content__box__cardImage
        = image_tag "cards/#{@card_src}", width: 40, height: 28
      .payment__content__box__details
        .payment__content__box__details__cardNumber
          = "カード番号:**** **** **** " + @card_info.last4
        .payment__content____boxdetails__cardMMYY
          = "有効期限:" + @exp_month + " / " + @exp_year
  .payment__cardDelete
    = button_to "削除する", card_path(@card.id), {method: :delete, id: 'charge-form', name: "inputForm", class: "payment__cardDelete__deleteBtn"}

次回は、登録できたカードでの決済機能を記載します。

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

ffi Library not loaded: /opt/brew/opt/libffi/lib/libffi.6.dylibのエラーを直す

概要

brew updateしたあとに発生したエラー
rspecが落ちる

今回の直し方

symlinkを貼る

採用理由

  • あんまり調べなくて良い
  • 早い

懸念点

  • updateのときに意図せずなんかなって嵌りそう

まあいいか

TODO: 公式issueにも一応載っているので今度読む。もしどなたか読んだ方いらしたらぜひ教えて下さいませ(他力本願...)
dyld: Library not loaded: /usr/local/opt/libffi/lib/libffi.6.dylib · Issue #7 · platformio/platform-lattice_ice40

エラー抜粋

LoadError:
  dlopen(/Users/user/dev/my-markdown/vendor/bundler/ruby/2.6.0/gems/ffi-1.11.1/lib/ffi_c.bundle, 9): Library not loaded: /opt/brew/opt/libffi/lib/libffi.6.dylib

探索

/opt/brew/opt/libffi/lib/libffi.6.dylib not loadedなので、言ってみる

cd /opt/brew/opt/libffi/lib/
➤ ls
libffi.7.dylib libffi.a       libffi.dylib   pkgconfig

確かにlibffi.6.dylibがない

symlinkを貼る

ln libffi.7.dylib libffi.6.dylib

直った

感想

多分、libffi.6.dylibになるように、何かしらreinstallなどしたほうがよいのだろうが、面倒なのでしないでおこう。

以上、またエラーが出たら対処する or issue今度読む方針の雑な直し方でした。

付録: エラー全容

➤ rspec spec/models/post_spec.rb:6

An error occurred while loading ./spec/models/post_spec.rb.
Failure/Error: require File.expand_path('../../config/environment', __FILE__)

LoadError:
  dlopen(/Users/user/dev/my-markdown/vendor/bundler/ruby/2.6.0/gems/ffi-1.11.1/lib/ffi_c.bundle, 9): Library not loaded: /opt/brew/opt/libffi/lib/libffi.6.dylib
    Referenced from: /Users/use(neocomplete_start_auto_complete)/dev/my-markdown/vendor/bundler/ruby/2.6.0/gems/ffi-1.11.1/lib/ffi_c.bundle
    Reason: image not found - /Users/user/dev/my-markdown/vendor/bundler/ruby/2.6.0/gems/ffi-1.11.1/lib/ffi_c.bundle
# ./vendor/bundler/ruby/2.6.0/gems/ffi-1.11.1/lib/ffi.rb:6:in `require'
# ./vendor/bundler/ruby/2.6.0/gems/ffi-1.11.1/lib/ffi.rb:6:in `rescue in <top (required)>'
# ./vendor/bundler/ruby/2.6.0/gems/ffi-1.11.1/lib/ffi.rb:3:in `<top (required)>'
# ./vendor/bundler/ruby/2.6.0/gems/sassc-2.3.0/lib/sassc/native.rb:3:in `require'
# ./vendor/bundler/ruby/2.6.0/gems/sassc-2.3.0/lib/sassc/native.rb:3:in `<top (required)>'
# ./vendor/bundler/ruby/2.6.0/gems/sassc-2.3.0/lib/sassc.rb:31:in `require_relative'
# ./vendor/bundler/ruby/2.6.0/gems/sassc-2.3.0/lib/sassc.rb:31:in `<top (required)>'
# ./vendor/bundler/ruby/2.6.0/gems/sassc-rails-2.1.2/lib/sassc/rails.rb:5:in `require'
# ./vendor/bundler/ruby/2.6.0/gems/sassc-rails-2.1.2/lib/sassc/rails.rb:5:in `<top (required)>'
# ./vendor/bundler/ruby/2.6.0/gems/sassc-rails-2.1.2/lib/sassc-rails.rb:3:in `require_relative'
# ./vendor/bundler/ruby/2.6.0/gems/sassc-rails-2.1.2/lib/sassc-rails.rb:3:in `<top (required)>'
# ./vendor/bundler/ruby/2.6.0/gems/administrate-0.13.0/lib/administrate/engine.rb:5:in `require'
# ./vendor/bundler/ruby/2.6.0/gems/administrate-0.13.0/lib/administrate/engine.rb:5:in `<top (required)>'
# ./vendor/bundler/ruby/2.6.0/gems/administrate-0.13.0/lib/administrate.rb:1:in `require'
# ./vendor/bundler/ruby/2.6.0/gems/administrate-0.13.0/lib/administrate.rb:1:in `<top (required)>'
# ./config/application.rb:22:in `<top (required)>'
# ./config/environment.rb:4:in `require_relative'
# ./config/environment.rb:4:in `<top (required)>'
# ./spec/rails_helper.rb:6:in `require'
# ./spec/rails_helper.rb:6:in `<top (required)>'
# ./spec/models/post_spec.rb:3:in `require'
# ./spec/models/post_spec.rb:3:in `<top (required)>'
# ------------------
# --- Caused by: ---
# LoadError:
#   cannot load such file -- 2.6/ffi_c
#   ./vendor/bundler/ruby/2.6.0/gems/ffi-1.11.1/lib/ffi.rb:4:in `require'
Run options: include {:locations=>{"./spec/models/post_spec.rb"=>[6]}}

All examples were filtered out

Randomized with seed 34574


Finished in 0.00064 seconds (files took 2.04 seconds to load)
0 examples, 0 failures, 1 error occurred outside of examples


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

Rails Console内で関数を定義したい

ちょっとしたことなんですが。

pryにコードをコピペするためにしておきたい設定#方法1:一つの式にしてしまう

括弧で括ると改行は無視されるようになります。
無視されるというか、閉じるまで式の途中とみなされます。

よく使うパターンとしては、Consoleが立ち上がってる間だけの関数を定義したいとき。
これはメソッドの一部を軽くテストしたいな〜ってときにやってます。

Console内で

[1] pry(main)> (
[1] pry(main)*   def hoge
[1] pry(main)*     return 'hoge' # 処理内容
[1] pry(main)*   end  
[1] pry(main)* )  
=> :hoge


[2] pry(main)> hoge # 呼び出し
=> "hoge"

という感じです。

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

【gem:devise の日本語化】

gem:devise日本語化の流れ

(deviseインストール後の流れ)

  1. gemのインストール
    ・gem 'devise-i18n'
    ・gem 'devise-i18n-views'

  2. コマンドで日本語翻訳ファイルを生成

  3. devise.views.ja.yml を編集

1. gemのインストール

gem 'devise-i18n'
gem 'devise-i18n-views'

gemfileに記述して“$ bundle install”します

2. コマンドで日本語翻訳ファイルを生成

$ rails g devise:views:locale ja

コマンドを実行すると日本語翻訳ファイルであるconfig/localesdevise.views.ja.yml
が生成されます。

3. devise.views.ja.yml を編集

生成されたconfig/localesdevise.views.ja.ymlを編集していきます。

config/localesdevise.views.ja.yml
ja:
  activerecord:
    errors:
      models:
        user:
          attributes:
            email:
              taken: "は既に使用されています。"
              blank: "が入力されていません。"
              too_short: "は%{count}文字以上に設定して下さい。"
              too_long: "は%{count}文字以下に設定して下さい。"
              invalid: "は有効でありません。"
            password:
              taken: "は既に使用されています。"
              blank: "が入力されていません。"
              too_short: "は%{count}文字以上に設定して下さい。"
              too_long: "は%{count}文字以下に設定して下さい。"
              invalid: "は有効でありません。"
              confirmation: "が内容とあっていません。"
    attributes:
      user:
        current_password: "現在のパスワード"
        name: 名前
        email: "メールアドレス"
        password: "パスワード"
        password_confirmation: "確認用パスワード"
        remember_me: "次回から自動的にログイン"
    models:
      user: "ユーザ"
  devise:
    confirmations:
      new:
        resend_confirmation_instructions: "アカウント確認メール再送"
    mailer:
      confirmation_instructions:
        action: "アカウント確認"
        greeting: "ようこそ、%{recipient}さん!"
        instruction: "次のリンクでメールアドレスの確認が完了します:"
      reset_password_instructions:
        action: "パスワード変更"
        greeting: "こんにちは、%{recipient}さん!"
        instruction: "誰かがパスワードの再設定を希望しました。次のリンクでパスワードの再設定が出来ます。"
        instruction_2: "あなたが希望したのではないのなら、このメールは無視してください。"
        instruction_3: "上のリンクにアクセスして新しいパスワードを設定するまで、パスワードは変更されません。"
      unlock_instructions:
        action: "アカウントのロック解除"
        greeting: "こんにちは、%{recipient}さん!"
        instruction: "アカウントのロックを解除するには下のリンクをクリックしてください。"
        message: "ログイン失敗が繰り返されたため、アカウントはロックされています。"
    passwords:
      edit:
        change_my_password: "パスワードを変更する"
        change_your_password: "パスワードを変更"
        confirm_new_password: "確認用新しいパスワード"
        new_password: "新しいパスワード"
      new:
        forgot_your_password: "パスワードを忘れましたか?"
        send_me_reset_password_instructions: "パスワードの再設定方法を送信する"
    registrations:
      edit:
        are_you_sure: "本当に良いですか?"
        cancel_my_account: "アカウント削除"
        currently_waiting_confirmation_for_email: "%{email} の確認待ち"
        leave_blank_if_you_don_t_want_to_change_it: "空欄のままなら変更しません"
        title: "%{resource}編集"
        unhappy: "気に入りません"
        update: "更新"
        we_need_your_current_password_to_confirm_your_changes: "変更を反映するには現在のパスワードを入力してください"
      new:
        sign_up: "アカウント登録"
    sessions:
      new:
        sign_in: "ログイン"
    shared:
      links:
        back: "戻る"
        didn_t_receive_confirmation_instructions: "アカウント確認のメールを受け取っていませんか?"
        didn_t_receive_unlock_instructions: "アカウントの凍結解除方法のメールを受け取っていませんか?"
        forgot_your_password: "パスワードを忘れましたか?"
        sign_in: "ログイン"
        sign_in_with_provider: "%{provider}でログイン"
        sign_up: "アカウント登録"
    unlocks:
      new:
        resend_unlock_instructions: "アカウントの凍結解除方法を再送する"

これで日本語化ができると思います!

おわりに

自分メモように書いてみました!
よかったら参考にしてみてください。

参照
https://remonote.jp/rails-devise-i18n-locale-ja

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

【Rails】DateとTimeの比較をする際の注意点

ことの起こりは深夜0時も回った頃。RSpecを走らせたところ、実装と関係のないところが落ちる。

調べてみたところ、どうやろここで本当はreturnしないといけないのにreturnしていないようです。

    return if last_tweet.tweeted_at > DateTime.yesterday

というわけで、デバッグしてみました。

デバッグ

[2] pry(#<RegisteredTag>)> DateTime.yesterday
=> Thu, 21 May 2020

うんうん、合ってる。

[3] pry(#<RegisteredTag>)> DateTime.yesterday.to_time
=> 2020-05-21 00:00:00 +0900

よかろう。

[4] pry(#<RegisteredTag>)> last_tweet.tweeted_at.to_time
=> 2020-05-21 00:30:02 +0900

そうだよね、では比較してみよう。

[6] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday.to_time
=> true

比較したらそうなるはずなんだ。では、元のコードに戻してみよう。

[5] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday
=> false

ファファファ????

ということで、改めて確認してみます。

日付の比較

[33] pry(main)> DateTime.yesterday
=> Thu, 21 May 2020
[34] pry(main)> after_9
=> Thu, 21 May 2020 09:21:38 +0900
[35] pry(main)> DateTime.yesterday < after_9
=> true
[37] pry(main)> before_9
=> Thu, 21 May 2020 08:31:48 +0900
[38] pry(main)> DateTime.yesterday < before_9
=> false

8時台 < DateTime.yesterday < 9時台
ということで、DateTime.yesterdayにはタイムゾーンが適用されていないようです。

Date.yesterdayでもやってみた

[22] pry(main)> Date.yesterday
=> Thu, 21 May 2020
[23] pry(main)> Date.yesterday < before_9
=> false
[24] pry(main)> Date.yesterday < after_9
=> true

DateTimeと同じ挙動をします。

クラスを見てみる

[31] pry(main)> Date.yesterday.class
=> Date
[32] pry(main)> DateTime.yesterday.class
=> Date

あ、DateTimeで作ってもインスタンスのクラスはDateになるんですね。
つまり、Date.yesterdayDateTime.yesterdayは同じもののようです。

結論

  • DateTimeで日付を指定すると、インスタンスはDateクラスになる。
  • DateクラスのインスタンスはDateTime/Timeクラスになった時点でタイムゾーンを持つ。
  • Dateクラスの状態ではタイムゾーンは+0000なので、DateTime/Timeクラス(RailsだとActiveSupport::TimeWithZoneクラス)と比較するときは注意する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】DateTimeで比較をする際の注意点

ことの起こりは深夜0時も回った頃。RSpecを走らせたところ、実装と関係のないところが落ちる。

調べてみたところ、どうやろここで本当はreturnしないといけないのにreturnしていないようです。

    return if last_tweet.tweeted_at > DateTime.yesterday

というわけで、デバッグしてみました。

デバッグ

[2] pry(#<RegisteredTag>)> DateTime.yesterday
=> Thu, 21 May 2020

うんうん、合ってる。

[3] pry(#<RegisteredTag>)> DateTime.yesterday.to_time
=> 2020-05-21 00:00:00 +0900

よかろう。

[4] pry(#<RegisteredTag>)> last_tweet.tweeted_at.to_time
=> 2020-05-21 00:30:02 +0900

そうだよね、では比較してみよう。

[6] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday.to_time
=> true

比較したらそうなるはずなんだ。では、元のコードに戻してみよう。

[5] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday
=> false

ファファファ????

ということで、改めて確認してみます。

日付の比較

[33] pry(main)> DateTime.yesterday
=> Thu, 21 May 2020
[34] pry(main)> after_9
=> Thu, 21 May 2020 09:21:38 +0900
[35] pry(main)> DateTime.yesterday < after_9
=> true
[37] pry(main)> before_9
=> Thu, 21 May 2020 08:31:48 +0900
[38] pry(main)> DateTime.yesterday < before_9
=> false

8時台 < DateTime.yesterday < 9時台
ということで、DateTime.yesterdayにはタイムゾーンが適用されていないようです。

Date.yesterdayでもやってみた

[22] pry(main)> Date.yesterday
=> Thu, 21 May 2020
[23] pry(main)> Date.yesterday < before_9
=> false
[24] pry(main)> Date.yesterday < after_9
=> true

DateTimeと同じ挙動をします。

クラスを見てみる

[31] pry(main)> Date.yesterday.class
=> Date
[32] pry(main)> DateTime.yesterday.class
=> Date

あ、DateTimeで作ってもインスタンスのクラスはDateになるんですね。
つまり、Date.yesterdayDateTime.yesterdayは同じもののようです。

結論

  • DateTimeで日付を指定すると、インスタンスはDateクラスになる。
  • Dateクラスはタイムゾーンの情報を持っていないため、+0000になる。
  • DateクラスはDateTime/Timeクラスになった時点でタイムゾーンを持つ。
  • DateクラスとDateTime/Timeクラス(RailsだとActiveSupport::TimeWithZoneクラス)を比較するときはDateクラスをDateTimeクラスへ変換する必要がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】DateTimeで日付と時間を比較をする際の注意点

ことの起こりは深夜0時も回った頃。RSpecを走らせたところ、実装と関係のないところが落ちる。

調べてみたところ、どうやろ本当はreturnしないといけないところがreturnしていないようです。
してほしい挙動としては、「最後にツイートした日時が昨日より最近のときにreturnする」というもの。

    return if last_tweet.tweeted_at > DateTime.yesterday

というわけで、調べてみました。

環境

Rails 5.2.4.2
Ruby 2.6.6

デバッグ

[2] pry(#<RegisteredTag>)> DateTime.yesterday
=> Thu, 21 May 2020

うんうん、合ってる。

[3] pry(#<RegisteredTag>)> DateTime.yesterday.to_time
=> 2020-05-21 00:00:00 +0900

よかろう。

[4] pry(#<RegisteredTag>)> last_tweet.tweeted_at.to_time
=> 2020-05-21 00:30:02 +0900

そうだよね、では比較してみよう。

[6] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday.to_time
=> true

比較したらそうなるはずなんだ。では、元のコードに戻してみよう。

[5] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday
=> false

ファファファ????

ということで、改めて確認してみます。

日付の比較

[33] pry(main)> DateTime.yesterday
=> Thu, 21 May 2020
[34] pry(main)> after_9
=> Thu, 21 May 2020 09:21:38 +0900
[35] pry(main)> DateTime.yesterday < after_9
=> true
[37] pry(main)> before_9
=> Thu, 21 May 2020 08:31:48 +0900
[38] pry(main)> DateTime.yesterday < before_9
=> false

8時台 < DateTime.yesterday < 9時台
ということで、DateTime.yesterdayにはタイムゾーンが適用されていないようです。

Date.yesterdayでもやってみた

[22] pry(main)> Date.yesterday
=> Thu, 21 May 2020
[23] pry(main)> Date.yesterday < before_9
=> false
[24] pry(main)> Date.yesterday < after_9
=> true

DateTimeと同じ挙動をします。

クラスを見てみる

[31] pry(main)> Date.yesterday.class
=> Date
[32] pry(main)> DateTime.yesterday.class
=> Date

あ、DateTimeで作ってもインスタンスのクラスはDateになるんですね。
つまり、Date.yesterdayDateTime.yesterdayは同じもののようです。

結論

  • DateTimeで日付を指定すると、インスタンスはDateクラスになる。
  • Dateクラスはタイムゾーンの情報を持っていないため、+0000になる。
  • DateクラスはDateTime/Timeクラスになった時点でタイムゾーンを持つ。
  • DateクラスとDateTime/Timeクラス(RailsだとActiveSupport::TimeWithZoneクラス)を比較するときはDateクラスをDateTimeクラスへ変換する必要がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアルのセットアップ関連だけ要約

Railsチュートリアルをようやく一周しました。
次の段階として、Railsチュートリアルを参考にしながらオリジナルのWebサービスを作ってみるために、セットアップ関連だけまとめています。
開発環境はCloud9です。

Rails環境の構築

便利コマンド

普段からドキュメントをインストールしないように指定するコマンド(らしい)。
参考にさせていただきました。
Railsチュートリアル第1章の「$ printf "install: --no-document \nupdate: --no-document\n" >> ~/.gemrc」を分解

$ printf "install: --no-document \nupdate:  --no-document\n" >> ~/.gemrc

Railsインストール

今回、バージョンはチュートリアルのままとしたが、いずれバージョンアップもやっておかないと。。。

$ gem install rails -v 5.1.6

rails newの実行

$ rails _5.1.6_ new "タイトル"

GitHub環境の構築

gitの生成

$ cd ~/envronment/"タイトル"
$ git init
$ git add -A
$ git commit -m "Initialize repository" # コメントは定番のもの

リポジトリの作成(GitHub)

GitHub上で 'New repository'してリポジトリを作成後、以下コマンドを実行(URLはリポジトリ作成画面に記載)

$ git remote add origin "URL"
$ git push -u origin master

# uオプションをつけて、origin masterを上流ブランチとして設定
# 次回以降、git pushのみでorigin masterへのpushが可能

リポジトリを非公開としたい場合は、Settingの一番下'Danger Zone'内にあるMake privateをクリック

SSH 公開鍵の設定(GitHub)

$ cd ~/.ssh          # 鍵を入れるフォルダに移動
$ ssh-keygen -t rsa  # 鍵を生成
$ cat id_rsa.pub     # 鍵の中身を表示して手動でコピー

# cloud9ではpbcopyが使用できなかったので、手動でコピーしています。

https://github.com/settings/ssh で公開鍵を設定
以下コマンドで接続を確認し、以下コメントが出れば接続完了。

$ ssh -T git@github.com

Hi (account名)! You've successfully authenticated, but GitHub does not provide shell access.

参考にさせていただきました。
GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~

警告がでた場合の対処方法

以下のような警告が出る場合があります(僕は出ました)。
Warning: Permanently added the RSA host key for IP address 'xxx.xxx.xxx.x' to the list of known hosts.

pushができなくなるわけではないので問題はなさそうですが、Worningがでているのは気持ち悪い。。。
以下コマンドでknown_hostsの内容が更新されてWorningがでなくなります。

$ ssh-keygen -R "IPアドレス"

# 上記で直らない場合は、以下コマンドも試してみてください

$ ssh-keygen -R github.com
push時に毎回パスワードを聞かれないための方法
  1. GitHubのCode画面の右側"Clone or download"ボタンをクリック
  2. 右上の"Use SSH"をクリックし、表示されたURLをコピー
  3. ターミナルから以下コマンドを実行
$ git remote set-url origin "URL"

# 確認のため以下を実行
$ git remote -v

#以下のように、git@github.comから始まっていれば成功!
origin  git@github.com:reiji012-pg/Tabetter.git (fetch)
origin  git@github.com:reiji012-pg/Tabetter.git (push)

Herokuのセットアップ

ここもひとまずチュートリアルのままで。
本番環境のみにPostgreSQLのgemをインストール

Gemfile.
group :production do
  gem 'pg', '0.20.0'
end

開発環境とテスト環境にSQLiteのgemをインストール

Gemfile.
group :development, :test do
  gem 'sqlite3', '1.3.13'
  gem 'byebug',  '9.0.6', platform: :mri
end

pg gemを本番環境にインストールしないようにオプションを追加してインストール

$ bundle install --without production

Herokuをインストール

$ source <(curl -sL https://cdn.learnenough.com/heroku_install)

インストールを確認後、ログインからデプロイまで。

$ heroku --version
$ heroku login --interactive
$ heroku keys:add
$ heroku create
$ git push heroku master

以下コマンドでアプリケーションの名称を変更可能

$ heroku rename "アプリケーション名"

とりあえずこれでアプリケーションの作成に入っていける環境が構築できたかと思います。

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

Allureを用いて言語、フレームワークに依存しないシャレオツなテストレポートを生成しよう!

概要

Allureについて、公式ドキュメントの意訳をベースに紹介します。

※ 言語、フレームワークに依存しないのは間違いないですが、本記事では便宜上 RSpec(Ruby) を用いて進めます。

Allureについて

テストフレームワークといえば、JUnit RSpec jest pytest など、様々な言語で数多くの優れたツールが用意されています。

しかし、多くのツールはビジュアルに優れたテストレポートを生成する機能は持ち合わせていません。

Allureはそれを補うための、言語やフレームワークに依存せずに、チームの誰もが簡単に理解できるテスト実行レポートを生成するためのOSSです。

デモ

百聞は一見にしかず。デモレポート を開いてみましょう。

スクリーンショット 2020-05-16 2.46.02.png

テスト実行レポートを、グラフィカル、インタラクティブに多くの観点から参照できることがわかります。

再三言いますが、これらの生成に言語、フレームワークは関係ありません。JUnitでも、RSpecjest、はたまたpytestなどからでも、現在運用中のテストコードをそのまま使用することができます。

レポートの作り方

Allureは一般的なxUnit(単体テストフレームワークの総称) の出力結果に加え、補助的なデータを付与したものに基づいています。

テストレポートは以下の2ステップで生成されます。

1. テスト実行

各種テストフレームワークがテストを実行する際、アダプターと呼ばれる軽量ライブラリによって、実行されたテストに関する情報をXMLやJSON形式で生成します。

アダプターJava,PHP,Ruby,Python,Scala,C#に対して公式から提供されており、それぞれ主要なテストフレームワークをサポートしています。

2. レポート生成

テスト完了後に、生成されたデータを元に、HTML形式のレポートを生成します。

レポートの生成はCLIツールから行うことが出来ます。

各種フレームワーク別の利用例

RSpec(Ruby)での実行例

本記事では便宜上、RSpecを例に進めますが、多くの言語、フレームワークでも同様のステップで進めることが出来ます。

CLIのインストール

レポートを生成するためのCLIツールであるallure本体をインストールします。今回はMacを使用するので、brewでインストールします。

$ brew install allure

バージョン確認ができていればOKです。

$ allure --version
2.13.0
$ allure --version
No Java runtime present, requesting install.

とか出る場合はJavaランタイムを入れる必要があります。

テストコードの用意

今回は以下のような Calculator クラスの振る舞いを検証する単体テストを用意します。

Calculator クラスは初期値を基準に、足し算と引き算を行えるクラスです。一つだけ意図的にテストが落ちるような設定になっています。

describe Calculator do
  subject { described_class.new(initial_value) }

  describe '#add 足し算' do
    context '初期値が0の場合' do
      let(:initial_value) { 0 }

      it '10を足すと10が戻る' do
        expect(subject.add(10)).to eq 10
      end
    end

    context '初期値が5の場合' do
      let(:initial_value) { 5 }

      it '10を足すと15が戻る' do
        expect(subject.add(10)).to eq 15
      end
    end
  end

  describe '#sub 引き算' do
    context '初期値が0の場合' do
      let(:initial_value) { 0 }

      # 落ちるテスト
      it '10を引くと0が戻る' do
        expect(subject.sub(10)).to eq 0
      end
    end

    context '初期値が30の場合' do
      let(:initial_value) { 30 }

      it '10を引くと20が戻る' do
        expect(subject.sub(10)).to eq 20
      end
    end
  end
end

普通にrpsecを実行すると以下の通り

$ bundle exec rspec spec/sample_spec.rb -f d
Calculator
  #add 足し算
    初期値が0の場合
      10を足すと10が戻る
    初期値が5の場合
      10を足すと15が戻る
  #sub 引き算
    初期値が0の場合
      10を引くと0が戻る (FAILED - 1)
    初期値が30の場合
      10を引くと20が戻る

Failures:

  1) Calculator#sub 引き算 初期値が0の場合 10を引くと0が戻る
     Failure/Error: expect(subject.sub(10)).to eq 0

       expected: 0
            got: -10

       (compared using ==)
     # ./sample_spec.rb:42:in `block (4 levels) in <top (required)>'

Finished in 0.11199 seconds (files took 2.06 seconds to load)
4 examples, 1 failure

Failed examples:

rspec ./sample_spec.rb:41 # Calculator#sub 引き算 初期値が0の場合 10を引くと0が戻る

アダプターを用いてレポート用データを生成する

Rspec実行時に、Ruby用のAllureアダプターを使って、レポート作成用のデータを生成してもらいます。

RSpec用のアダプタはallure-rspecを使用します。

今回はGemfileに以下を追記してインストールします。

gem "allure-rspec"

spec_helper にてアダプタを読み込み

require "allure-rspec"

実行時にアダプターをフォーマッタとして指定します。今度は標準出力には何も出力されません。

$ bundle exec rspec spec/sample_spec.rb -f AllureRspecFormatter

デフォルトだとreportsディレクトリが作成され、以下のような構造でレポート用データが生成されています。

$ tree reports/
reports/
└── allure-results
    ├── 4ee53de0-790a-0138-ab5e-3d13be3ed9ba-container.json
    ├── 4ee54750-790a-0138-ab5e-3d13be3ed9ba-container.json
    ├── 4ee54a80-790a-0138-ab5e-3d13be3ed9ba-container.json
    ├── 4ee64530-790a-0138-ab5e-3d13be3ed9ba-result.json
    ├── 4ee7fad0-790a-0138-ab5e-3d13be3ed9ba-container.json
    ├── 4ee80710-790a-0138-ab5e-3d13be3ed9ba-result.json
    ├── 4ee83ae0-790a-0138-ab5e-3d13be3ed9ba-container.json
    ├── 4ee83e90-790a-0138-ab5e-3d13be3ed9ba-container.json
    ├── 4ee84860-790a-0138-ab5e-3d13be3ed9ba-result.json
    ├── 4eefe9b0-790a-0138-ab5e-3d13be3ed9ba-container.json
    └── 4eeff800-790a-0138-ab5e-3d13be3ed9ba-result.json

1 directory, 11 files

レポートを生成する

前項で出力したレポート用データを用いて、allureコマンドでレポートを生成します。

serve サブコマンドを使うことで、ローカルでレポートを生成し、そこにブラウザでアクセスすることができるようになります。

$ allure serve -h 0.0.0.0 ./reports/allure-results/
Generating report to temp directory...
Report successfully generated to /var/folders/3_/r5h56zzn1130yr7vkpjftqpw0000gp/T/15980098365829452874/allure-report
Starting web server...
2020-05-16 04:02:58.509:INFO::main: Logging initialized @3541ms to org.eclipse.jetty.util.log.StdErrLog
Server started at <http://0.0.0.0:57717/>. Press <Ctrl+C> to exit

上記の例だと、 0.0.0.0:57717 にアクセスすると、無事にレポートが生成されていることが確認できました。

スクリーンショット 2020-05-16 4.05.59.png
スクリーンショット 2020-05-16 4.05.40.png

所感

本記事の題の通り、言語、フレームワークに依存しないことから非常に多機能で汎用的になってしまうため、個別用途としてはどうしてもToo muchになってしまう印象でした。

最初からAllureを想定したテスト設計が出来ると上手く刺さるかもしれませんが、綺麗に構造化されてない限りは既存のテストコードを載せるのは難しそう。

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

DaprでRubyとJavaを連携させてみる

はじめに

マイクロサービス開発を容易にするDaprをJavaで利用してみたに引き続きDaprです。

チュートリアルのStep6に相当します。
コードは下記を参照
https://github.com/koduki/example-dapr/tree/v01

構成概要

チュートリアルではPythonですが、趣味の問題でRubyを使っています。
この構成のポイントはRubyとJavaは直接通信しないどころはRubyはあくまで自分のサイドカーのDaprと話すのであってJavaを起動しているDaprとは直接話さないことです。

dapr_sample01.png

テストがローカルなので実は直接話すこともできるのですが、分かりやすくJavaのDaprポートを3500, RubyのDaprポートを3600とします。

コード

今回は1秒に一度、対象APIにリクエストを投げるだけの超シンプルなコードです。
javaappはJavaアプリのappidです。こちらを使ってDparどうして適切なアプリを自動で発見します。APIが動いているIPとかポート番号の指定は不要です。
Rubyコード内で指定しているhttp://localhost:#{MY_DAPR_PORT}はJava側のDaprではなくRuby側のDaprです。なので常にlocalhostですし、ポート番号も今回で言えば3500では無く3600です。

require 'net/https'
require "json"

MY_DAPR_PORT=ARGV[0]

n = 0
while true do
    n += 1
    params = {data: {orderId: n}}

    url = "http://localhost:#{MY_DAPR_PORT}/v1.0/invoke/javaapp/method/neworder"
    uri = URI.parse(url)
    http = Net::HTTP.new(uri.host, uri.port)

    headers = { "Content-Type" => "application/json" }
    response = http.post(uri.path, params.to_json, headers)

    sleep 1
end

実行&確認

では実行してみましょう。DaprはWebアプリだけでは無くコマンドも問題なくラッピングできるようです。
まず、API側を立ち上げます。

$ dapr run --app-id javaapp --app-port 8080 --port 3500 ./mvnw quarkus:dev

続いて別ターミナルでクライアント側を立ち上げます。

$ dapr run --app-id rubyapp --port 3600 ruby app.rb 3600

Java側の実行結果を見ると以下のようになっています。

== APP == orderId: 1
== APP == orderId: 2
== APP == orderId: 3

ちゃんと毎秒定期実行されてるのが分かりますね。

dapr listを実行することで今起動しているDaprの一覧も確認できます。

$dapr list
  APP ID   HTTP PORT  GRPC PORT  APP PORT  COMMAND             AGE  CREATED              PID    
  rubyapp  3600       55932      0         ruby app.rb 3600    28m  2020-05-24 21:08.13  44897  
  javaapp  3500       56777      8080      ./mvnw quarkus:dev  3d   2020-05-21 21:21.30  27471  

まとめ

上手くRubyとJavaでDaprを経由して通信ができました。

このアーキテクチャの面白いところはRubyコードもJavaコードもあくまでDaprとしか会話してないのでその間はHTTPなりgRPCなりでセキュリティをさほど気にしなくて良いことです。
通信の暗号化とか認証とかはDapr同士がやれば問題ないのでセキュリティに関する部分を外だしできるのは大きいですね。

分散トレーシングとかTwitter APIやKafkaなど他のコネクタもあるはずなのでそこら辺も試してみたいと思います。
同じマイクロサービス支援ということでKNativeと似たレイヤーかと最初は思ってたのですが、あちらはビルド環境やFaaS, Servelessと言ったインフラ観点よりなのに対し、こちらはかなりアプリより仕組みなのでCNCFにも採択されてますし今後が気になるツールなのは間違い無いですね。

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

Ruby Python Java 大文字小文字を区別しないソート

はじめに

ソートと言えば、数値順・辞書順ですが、それ以外のソートについて調べてみました。

嵌った話

@jag_507 さんのAtCoderでRuby学習10【第一回アルゴリズム実技検定 DoubleCamelCase Sort】を読んで、AtCoder 第一回アルゴリズム実技検定 F - DoubleCamelCase Sortをやってみましたが、ソートがうまくいかないです。

sort.rb
a = ["FisH", "DoG", "CaT", "AA", "AaA", "AbC", "AC"]
a.sort
 # => ["AA", "AC", "AaA", "AbC", "CaT", "DoG", "FisH"] # 実際の返り値
      ["AA", "AaA", "AbC", "AC", "CaT", "DoG", "FisH"] # 期待する返り値

この記事:sortコマンド、基本と応用とワナの様な-fオプションはないのか、ドラえもんGoogle先生助けてー。

大文字小文字を区別しないソート

Ruby に限らず多くのプログラミング言語の辞書順のソートはASCIIコード順での並び替えになりますので、大文字と小文字では大文字が若くなります。
そこで、大文字小文字を区別しないソートが必要になります。

Ruby

ruby.rb
a = ['a', 'b', 'c', 'd', 'e', 'A', 'B', 'C', 'D', 'E']
p a.sort
 # => ["A", "B", "C", "D", "E", "a", "b", "c", "d", "e"]
p a
 # => ["a", "b", "c", "d", "e", "A", "B", "C", "D", "E"]
p a.sort{|x, y| x.casecmp(y).nonzero? || x <=> y}
 # => ["A", "a", "B", "b", "C", "c", "D", "d", "E", "e"]
p a.sort_by{ |s| [s.downcase, s] }
 # => ["A", "a", "B", "b", "C", "c", "D", "d", "E", "e"]

casecomp が大文字小文字を区別しないメソッドになります。
追記
コメント欄よりsort_byの解法をいただきました。
最優先s.downcaseと次優先sの2つ条件でソートを行っています。

Python

python.py
a = ['a', 'b', 'c', 'd', 'e', 'A', 'B', 'C', 'D', 'E']
print(sorted(a))
 # => ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']
print(a)
 # => ['a', 'b', 'c', 'd', 'e', 'A', 'B', 'C', 'D', 'E']
print(sorted(sorted(a), key=str.lower))
 # => ['A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e']

ソート HOW TO に、大文字小文字を区別しない文字列比較の例が載っています。
Python のソートは安定なことが保証されていますので、2重にソートする方法が考えられます。

Java

java.java
        List<String> a = Arrays.asList("a", "b", "c", "d", "e", "A", "B", "C", "D", "E");
        a.sort(Comparator.naturalOrder());
        System.out.println(a); // [A, B, C, D, E, a, b, c, d, e]
        a.sort(String.CASE_INSENSITIVE_ORDER);
        System.out.println(a); // [A, a, B, b, C, c, D, d, E, e]

Java の場合、CASE_INSENSITIVE_ORDERが準備されています。

まとめ

  • 大文字小文字を区別しないソート に詳しくなった
  • Ruby に詳しくなった
  • Python に詳しくなった
  • Java に詳しくなった
  • 自然順のソートまで手が回らなかった

参照したサイト
AtCoderでRuby学習10【第一回アルゴリズム実技検定 DoubleCamelCase Sort】
sortコマンド、基本と応用とワナ
casecomp
ソート HOW TO
[Java 8]コーディングテストで使えるアルファベット順、文字列長順のソート方法

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

Railsでよく使うRubyのメソッド

はじめに

Ruby on Railsをメインに扱うWeb受託開発企業に転職し、約4ヶ月が経過しました。
初心者ながらにプロジェクトにアサインして頂き苦し楽しい毎日を過ごしております。
開発していく中でよく使うなーと思うRubyのメソッドをまとめていきます!

メソッド一覧

if

説明不要の条件分岐メソッド。
true,falseで評価し、実行する処理を分岐させる。

if 条件式
  ifに対するtrue処理
elsif 条件式
  elsifに対するtrue処理
else
  falseの処理
end

each

配列の要素を繰り返しの形で取得するメソッド。
RailsではDBのレコードを順に取得する際に利用しています。

[1, 2, 3].each do |i|
  p i
end
出力
1
2
3

map

こちらもeachメソッドと似ていますが、
DBのレコードを取得する際にカラム指定する際に利用しています。

例えば以下のテーブルがあった場合

Usersテーブル
id |name        | age  | sex  | 
1  | テスト 一郎 | 20  | 男   |
2  | テスト 花子 | 18  | 女   |
3  | テスト 二郎 | 15  | 男   |

このようにカラムを指定して取り出せます。

Users.map(&:age)

出力
[20, 18, 15]

カラム指定でテーブルのid等を取得して、
関連付けされたテーブルを参照したりするのに便利です。
また、Whereメソッドと組み合わせ使用したりもよくします。

where

DBからレコードを取り出す際にカラムとデータを指定して検索する際に使用します。
mapと同様のテーブルが存在した場合、以下の通りに取得できます。

Users.where(age: 15)

出力
id |name        | age  | sex  |
3  | テスト 二郎 | 15  | 男   |

first

複数レコードが存在するDBの最初のレコードを取得します。
mapと同様のテーブルが存在した場合、以下の通りに取得できます。

Users.first

出力
id |name        | age  | sex  | 
1  | テスト 一郎 | 20  | 男   |

present?,blank?,nil?

present?:値が存在すればtrue,存在しなければfalse。配列を評価する際に[]の場合nilとなりfalseとなる。
blank?:値といえるものがない場合、真となる。
nil?:変数の値がnilまたは、値なしの場合、真となります。

三項演算子

ifによる条件分岐が1度しかない場合によく利用します。
1行で条件分岐することができ、個人的にはスマートだと思います。

条件式? trueの処理 : falseの処理

count

DBのレコードをカウントしたり、ページネーションに利用したりします。
mapと同様のテーブルが存在した場合、以下の通りに取得できます。

Users.count

出力
3

参考

Ruby 2.7.0 リファレンスマニュアル
【Ruby入門】真偽判定present?の使い方をわかりやすく解説

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

Rubyのバージョンを変えたらbundle installができなくなった

事件発生

ある日のことだった

$ bundle install

を入力すると

Traceback (most recent call last):
~/.rbenv/versions/2.5.0/bin/ruby: invalid option -:  (-h will show valid options) (RuntimeError)

ええええ何だこれ!
他のコマンドも受け付けてくれない、何をしでかしたんだ…

これまでを振り返ってみよう

自分が致命的な何かをしてしまったはず…

Ruby : 2.7 で開発していた

(自分の環境)/vendor/bundle/ruby/2.7.0/gems/active_hash-3.1.0/lib/associations/associations.rb:22: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
(自分の環境)/vendor/bundle/ruby/2.7.0/gems/activerecord-6.0.3.1/lib/active_record/associations.rb:1657: warning: The called method `belongs_to' is defined here

Railsコマンドを入力すると警告がたくさん出て鬱陶しいな
gemの警告は大事だけどまあいっか

環境変数を追加して対処

export RUBYOPT='-W:no-deprecated -W:no-experimental'

.zshrcに上を追加したら警告が消えたよストレスフリーだね

Rubyのバージョンを下げて開発しなければならなくなった

別バージョンでの開発が不可欠になったのでバージョンを下げました(Ruby:2.5)

Traceback (most recent call last):
~/.rbenv/versions/2.5.0/bin/ruby: invalid option -:  (-h will show valid options) (RuntimeError)

今までは別のバージョンにしてもこんなこと起きなかったのにな…

結論

export RUBYOPT='-W:no-deprecated -W:no-experimental'

環境変数からこれを削除したら解決しました。
gemのバージョンが上がって警告が出なくなったら忘れずに削除しないとね。

参考

https://qiita.com/mojihige/items/ce3282bec0c58f8ba637
https://qiita.com/hirocueki2/items/010c777d2125ee120a8e

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

[rails]Rspecで単体テストを行おう!

テストには、単体テストと統合テストの2種類がある。

-単体テスト
 1つのプログラムに対して、正常に動くかのテスト。
 (例)モデルクラスごと

-統合テスト
 1蓮の処理に関するテスト。
 (例)ユーザーの新規登録用画面から値を入力、送信して、データベースにレコードが追加されるまでの流れ

「Rspec」と「factory_bot」の導入

「Rspec」はテスト行うためのjem、
「factory_bot」はテストを行う際に一時的に情報を作成してくれる
お助けツールです。

そのため、まとめてインストールしましょう。

①jemno
インストール

Gemfile
group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end
ターミナル
bundle install

②RSpecの最低限必要なファイル/ディレクトリ構成をRailsにインストール

ターミナル
$ rails g rspec:install
#>      create  .rspec                  # RSpecの設定ファイル
#>      create  spec                    # スペックを格納する
#>      create  spec/spec_helper.rb     # スペック記述のためのヘルパ
#>      create  spec/rails_helper.rb    # Rails固有のスペック記述のためのヘルパ

specフォルダ.png

ここに、必要なディレクトリ・ファイルを追加していきます。

③必要なファイルの作成
ここでは、usersモデルに対してのバリデーションテストを
行なっていきます。

◆テスト用ファイル
spec/内のファイルは、app/以下のテスト対象のrbファイルに対して1
対1で対応するかたちで配置します。

app/models/user.rbに対するスペックは
spec/models/user_spec.rbとなります。

spec-file.png

◆テストのためのデータ用ファイル(factory_bot)
spec/factoriesにファクトリを配置することで、
簡単にテスト用のデータを利用できます。

factories.png

④ファイルの記述

名前空間を省略できるように設定する。

spec/rails_helper.rb
RSpec.configure do |config|
+  config.include FactoryGirl::Syntax::Methods
end
.rspec
--format documentation
--require spec_helper
spec/models/user_spec.rb
spec/factories/users.rb
FactoryBot.define do
  factory :user do
    nickname               {"taro"}
    email                  {"kkk@gmail.com"}
    password               {"00000000"}
    password_confirmation  {"00000000"}
  end
end

⑤テストの実行

ターミナル
bundle exec rspec spec/models/user_spec.rb
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む