20200919のRubyに関する記事は21件です。

Shopifyのカスタムアプリでwebhookを設定する

はじめに

この記事ではShopifyのストア内で起こる特定のeventをrailsで作成したカスタムアプリと連動させるために使用したwebhookの設定についてお話しします。
webhookの通知を受けるためには管理画面上で設定→通知からマニュアルで設定する方法もありますが、アプリを作ったストア全ての管理画面に入れるわけではないこと、公開アプリとして運用したい人たちのために、今回は管理画面からの設定はしないこととします。

環境

Ruby 2.6.6
Rails 6.0.2

1. 必要となるアクセススコープをconfigureする

shopify_app.rb

ShopifyApp.configure do |config|
  ...
  config.scope = "read_products, read_orders" 
  ]
  ...
end

※ここで注意しなければいけないのが、この時点ですでにストアにアプリがインストールされている場合は、スコープを増やした後にアプリのアンインストール→再インストールをした方が良いという点です。

2.通知を受けたいwebhookとリクエスト先のURLを指定

gem 'shopify_app'をインストールしていることが前提でコマンドラインで下記を指定

rails g shopify_app:add_webhook -t orders/create -a https://example.com/webhooks/orders_create

この後shopify_app.rbには下記が追加される

ShopifyApp.configure do |config|
  ...
  config.webhooks = [
    {topic: 'orders/create', address: 'https://example.com/webhooks/orders_create'},
  ]
  ...
end

3.Jobの設定

それぞれのwebhookでjobを作成します
app/jobs/orders_create_job.rb

class OrdersCreateJob < ActiveJob::Base
  def perform(shop_domain:, webhook:)
    shop = Shop.find_by(shopify_domain: shop_domain)

    shop.with_shopify_session do
      # ここでimplementしたい機能を追加
    end
  end
end

sidekiq等の非同期処理を行うライブラリを使用されいる場合は、上記のjobがbackgroundで処理されます。

4.webhook用のカスタムcontrollerを作成

この部分はなくても問題ないのですが、webhookを通じてjobで行う以上の機能が必要となる場合などに必要となるので記載します。
routes.rb

...
post 'webhooks/orders_create', :to => 'custom_webhooks#orders_create'
...

app/controllers/custom_webhooks_controller.rb

class CustomWebhooksController < ApplicationController

  def orders_create
  end
  ...

  private

  def webhook_params
    params.except(:controller, :action, :type)
  end

end
  • Webhook verification 全てのwebhookはアプリがインストールされたストアからのリクエストであることを証明する必要があります。 Shopify側では、これをhttpリクエストのheaderにHMACを入れることでこれを証明するようにしています。 railsアプリの場合、幸いにも下記を入れるだけでこのHMACを識別して正しいリクエストかどうかを判断してくれます。
class CustomWebhooksController < ApplicationController
  include ShopifyApp::WebhookVerification
  ...
end
  • 必要となるmethodを作成
def orders_create
  params.permit!
  OrdersCreateJob.perform_later(shop_domain: shop_domain, webhook: webhook_params.to_h)
  head :no_content
end

webhookを通じてPOSTコールがされたあと、Shopify側に200 series statusを返す必要があります。(公式ドキュメントによると200 OK以外のレスポンスを返した場合には、48時間以内に19回のコールをしてくれるようです)
head :no_content にすることで、レスポンスは "204 no content"=どのデータもレスポンスしていない=webhookのデータは受領されたと判断されます

注意点

最初に管理画面にてwebhookの通知を設定をしている場合でも、config上で必要となるscopeのアクセスを許可されていなければhttpリクエストすらされないようでした。
ご自身のアプリがすでにストアにインストールされてしまっている場合には、このscope追加でアプリが再インストールされる際に下記のような画面でスコープ許可がされることを確認してください。
image.png

webhookがストアに設定されているかを確認する場合は、

header
Content-Type: application/json
X-Shopify-Access-Token:  該当のshop.shopify_token
GET https://ストア名.myshopify.com/admin/api/2020-07/webhooks.json

で確認することもできます。

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

【ruby】例外処理で、施されたら施し返す

例えばあるデータをパースして、特定のデータのみデータベースに保存したいとする。
その中で、パースできない拡張子があればエラーが出て、処理が中断してしまいバグを引き起こしてしまう。

そこで、便利なのが例外処理というもので、処理を中断させず例外事案の場合の処理を施すことができる。

まさに、施すこされたら施し返す、恩返しですを実現できるのだ。

今回は単純な計算を例に、例外処理を見ていこう。

エラーが発生した場合の処理 begin / rescue

Ruby内で、10を0で割ろうとするとエラーが出てしまう。

puts 10 / 0
puts "こんにちわ"
divided by 0 (ZeroDivisionError)

処理が途中で、中断されてしまう。

そこで、
- エラーの対象になりそうな箇所を、beginで囲う。
- エラーが発生した時の処理を、rescue内に記述する。

begin
  10 / 0
rescue
  p "0で割れません"
end

puts "こんにちわ"
"0で割れません"
"こんにちわ"

処理が中断されず、エラー時の処理と正常処理どちらも実行された。

省略法

beginなしで、rescueさせる

puts 10 / 0 rescue 0
puts 10 / nil rescue 0 
0
0

エラー内容を、変数に格納 rescue =>

begin
  10 / 0
rescue => e
  puts e
end

puts "こんにちわ"
divided by 0
こんにちわ

エラーオブジェクトが、変数eに格納されて出力できる。

エラーごとの処理を変える rescue "エラーメッセージオブジェクト"

begin
  10 / 0
rescue NoMethodError
  puts "そのようなメソッドはない"
rescue ZeroDivisionError
  puts "0で割れません"
end
0で割れません

2番目のエラーメッセージに該当するので、そのrescueに反応する。

注意点

対象の例外クラスの親が、先に記述されいた場合はそちらが先に処理される

begin
  10 / 0
rescue StandardError
  puts "基本的なエラー"
rescue ZeroDivisionError
  puts "0で割れません"
end
基本的なエラー

明示的なエラーを発生させて、処理を中断させる raise

使用用途は、
1. パラメータが想定されたものでないとき
2. 不正なアクセスがきた時

begin
  raise NoMethodError
rescue => e
  p e
end
NoMethodError

独自のエラーを発生させる

例外クラス(StandardErrorクラス)を継承させてあげる。

class Hoge < StandardError
end

begin
  raise Hoge
rescue => e
  p e
end
#<Hoge: Hoge>

エラー時でも、再度初めから実行させる retry

num = 0

begin
  puts 10 / num
rescue ZeroDivisionError => e
  puts e
  num = 1
  retry
end

puts "終了しました"
divided by 0
10
終了しました

ループ1回目では、エラーが発生していて、
ループ2回目では、正常に処理がされている。

エラーが発生しても、しなくても行う処理 ensure

begin
  puts "例外なし"
rescue => e
  puts e
ensure
  puts "Hello"
end
例外なし
Hello

ensureは、いかなる時でも必ず実行される。

番外編 エラーオブジェクトとは

begin
  10 / 0
rescue => e
  puts e.class
  puts e.class.superclass
  puts e.class.superclass.superclass
  puts e.class.superclass.superclass.superclass
end
ZeroDivisionError
StandardError
Exception
Object

Exceptionが元となるクラスで、Objectがその親

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

(ギリ)20代の地方公務員がRailsチュートリアルに取り組みます【第11章】

前提

・Railsチュートリアルは第4版
・今回の学習は3周目(9章以降は2周目)
・著者はProgate一通りやったぐらいの初学者

基本方針

・読んだら分かることは端折る。
・意味がわからない用語は調べてまとめる(記事最下段・用語集)。
・理解できない内容を掘り下げる。
・演習はすべて取り組む。
・コードコピペは極力しない。

 
 認証システム開発・第6段回目、第11章に入ります。セキュリティ強化の観点から、アカウント有効化のステップを入れていきます。よくある、登録後にメールが送られてきて、そのリンクを踏むと本登録になるやつですね。
 
本日のBGMはこちら。
Tatuki Seksu "Hanazawa EP"
いろいろあやしいのをシューゲサウンドでパッケージングしましたってかんじが好き。

 

【11.1.1 AccountActivationsコントローラ 演習】

1. 現時点でテストスイートを実行すると greenになることを確認してみましょう。
→ 現時点ではテスト書いてないのでGREENです。

 
2. 表 11.2の名前付きルートでは、_pathではなく_urlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: 私達はこれからメールで名前付きルートを使います。
→ pathは相対パス(/とか省略した形)、urlは絶対パス(https:~~とか完全な形)。メールというrails外の処理にはurlの完全形である絶対パスが必要と考えます。

 

【11.1.2 AccountActivationのデータモデル メモと演習】

コールバックではbefore_〇〇の、〇〇の動作の直前に特定のメソッドを実行することができる。メソッド参照という。ブロックを渡すよりこちらが推奨。
(おさらい)privateより下にメソッドを定義することで、外部に非公開にできる。

1. 本項での変更を加えた後、テストスイートが green のままになっていることを確認してみましょう。
→ GREEN

 
2. コンソールからUserクラスのインスタンスを生成し、そのオブジェクトからcreate_activation_digestメソッドを呼び出そうとすると (Privateメソッドなので) NoMethodErrorが発生することを確認してみましょう。また、そのUserオブジェクトからダイジェストの値も確認してみましょう。
→ こんなかんじ

>> user = User.third
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 2]]
=> #<User id: 3, name: "Mr. Sage Hartmann", email: "example-2@railstutorial.org", created_at: "2020-09-17 08:34:09", updated_at: "2020-09-17 08:34:09", password_digest: "$2a$10$.HyqPb.DwmFICve62DsYte1alLAVihIdeS2F8Rjndry...", remember_digest: nil, admin: false, activation_digest: "$2a$10$9VKv/p9kYrz84SdMs/7s/uzEV3mqzGMmTubIq7.Vz4b...", activated: true, activated_at: "2020-09-17 08:34:09">
>> user.create_activation_digest
Traceback (most recent call last):
        1: from (irb):2
NoMethodError (private method `create_activation_digest' called for #<User:0x000000000426b7a0>)
Did you mean?  restore_activation_digest!
>> user.activation_digest
=> "$2a$10$9VKv/p9kYrz84SdMs/7s/uzEV3mqzGMmTubIq7.Vz4bbIb.ZeLDRy"

 
3. リスト 6.34で、メールアドレスの小文字化にはemail.downcase!という (代入せずに済む) メソッドがあることを知りました。このメソッドを使って、リスト 11.3のdowncase_emailメソッドを改良してみてください。また、うまく変更できれば、テストスイートは成功したままになっていることも確認してみてください。
→ email.downcase!に変えるだけやね。

user.rb
    def downcase_email
      email.downcase!
    end

 

【11.2.1 アカウント有効化のメール送信 演習】

1. コンソールを開き、CGIモジュールのescapeメソッド (リスト 11.15) でメールアドレスの文字列をエスケープできることを確認してみましょう。このメソッドで"Don't panic!"をエスケープすると、どんな結果になりますか?
→ 下記。(クエリパラメータとCGIは用語集に入れてます)

>> CGI.escape('foo@example.com')
=> "foo%40example.com"
>> CGI.escape("Don't panic!")            
=> "Don%27t+panic%21"

 

【11.2.2 送信メールのプレビュー メモと演習】

 AWScloud9を使用している場合、チュートリアルでは旧cloud9使用しているので'example.com'に入力する内容の見た目がかなり違うので戸惑いますが、内容は一緒です。Railsサーバーを立ち上げて、別タブで表示した画面のURLのhttps://以下をすべてコピペすればOKです。

1. Railsのプレビュー機能を使って、ブラウザから先ほどのメールを表示してみてください。「Date」の欄にはどんな内容が表示されているでしょうか?
→ 今日の日付とUTC(協定世界時)が表示される。(協定世界時って言葉カッコよくないですか?中二心をくすぐられる)

 

【11.2.3 送信メールのテスト メモと演習】

 ここのassert_matchがいまひとつしっくり来ないですが、「メールの中身に対し、名前と有効化トークンとエスケープしたメルアドが含まれているかテストしている」ぐらいに認識しておきます。

1. この時点で、テストスイートが greenになっていることを確認してみましょう。
→ Yes, GREEN !

 
2. リスト 11.20で使ったCGI.escapeの部分を削除すると、テストが redに変わることを確認してみましょう。
→ REDになるのは、メールアドレスにメタ文字が含まれていて、エスケープしないと正規表現ではないからですかね。こちらの記事参照。

 

【11.2.4 ユーザーのcreateアクションを更新 メモと演習】

deliver_now:名前のとおり、その瞬間にメール送信処理を実行。

1. 新しいユーザーを登録したとき、リダイレクト先が適切なURLに変わったことを確認してみましょう。その後、Railsサーバーのログから送信メールの内容を確認してみてください。有効化トークンの値はどうなっていますか?
→ homeに戻りました。Welcome to the Sample App! Click on the link below to activate your account: 以下のURLに含まれているランダムな文字列が有効化トークンです。

 
2. コンソールを開き、データベース上にユーザーが作成されたことを確認してみましょう。また、このユーザーはデータベース上にはいますが、有効化のステータスがfalseのままになっていることを確認してください。
→ activated: false になっていました。

 

【11.3.1 authenticated?メソッドの抽象化 メモと演習】

 もう何のダイジェストの話してんのか混乱してきましたね。ってなったら11章の始めにあった表11.1を見返しましょう。何の話をしているのか、しっかり把握しながら進めないと。
 そんで、この節のタイトルは抽象化より一般化ってかんじがする。authenticated?メソッドを表11.1のどのパターンでも使用できるようにするわけやし。

1. コンソール内で新しいユーザーを作成してみてください。新しいユーザーの記憶トークンと有効化トークンはどのような値になっているでしょうか? また、各トークンに対応するダイジェストの値はどうなっているでしょうか?
→ こんな感じっすね。ユーザー作っただけだから記憶系はnilのまま…

>> user = User.create(name: "muteki", email: "muteki@man.com", password: "mutekimuteki", password_confirmation: "mutekimuteki")
   (0.1ms)  SAVEPOINT active_record_1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "muteki@man.com"], ["LIMIT", 1]]
  SQL (2.5ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest") VALUES (?, ?, ?, ?, ?, ?)  [["name", "muteki"], ["email", "muteki@man.com"], ["created_at", "2020-09-17 12:57:50.404544"], ["updated_at", "2020-09-17 12:57:50.404544"], ["password_digest", "$2a$10$eDPAP444JbjJDGucKnoFE.MWFBTAR8dxQ.wXPJfzql9E0TPRVDQfq"], ["activation_digest", "$2a$10$zql927sHRszT.bjitRxBn.slJil.Zvc74AJkztqBZzt7kUiSqBgx."]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<User id: 102, name: "muteki", email: "muteki@man.com", created_at: "2020-09-17 12:57:50", updated_at: "2020-09-17 12:57:50", password_digest: "$2a$10$eDPAP444JbjJDGucKnoFE.MWFBTAR8dxQ.wXPJfzql9...", remember_digest: nil, admin: false, activation_digest: "$2a$10$zql927sHRszT.bjitRxBn.slJil.Zvc74AJkztqBZzt...", activated: false, activated_at: nil>

なので、記憶系をわざわざ作ります。

>> remember_token = User.new_token
=> "5dyf7BoW9H3H9SYH6VPRYg"
>> remember_digest = User.digest(remember_token)
=> "$2a$10$1CymqXEPzP.b05TblQ3Zye/ukhNblEpGlDxI4kT2VoiLUJK1EHVy2"
>> user.update_attribute(:remember_token, remember_token)
   (0.1ms)  SAVEPOINT active_record_1
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
>> user.update_attribute(:remember_digest, remember_digest)
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.2ms)  UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ?  [["updated_at", "2020-09-17 13:45:51.944577"], ["remember_digest", "$2a$10$1CymqXEPzP.b05TblQ3Zye/ukhNblEpGlDxI4kT2VoiLUJK1EHVy2"], ["id", 102]]
   (0.0ms)  RELEASE SAVEPOINT active_record_1
=> true

ということで、各トークンはこんな感じ。ダイジェストは上で出てます。

>> user.remember_token
=> "5dyf7BoW9H3H9SYH6VPRYg"
>> user.activation_token
=> "p5rorOE7trfF4L-YJynnxg"

 
2. リスト 11.26で抽象化したauthenticated?メソッドを使って、先ほどの各トークン/ダイジェストの組み合わせで認証が成功することを確認してみましょう。
→ 確認するだけ…ってあれー??activation_tokenでNameError??
って調べたら解決。あくまでユーザーに紐づいてるものだからuser.activation_tokenか。

>> user.authenticated?(:remember, remember_token)
=> true
>> user.authenticated?(:activation, activation_token)
Traceback (most recent call last):
        1: from (irb):11
NameError (undefined local variable or method `activation_token' for main:Object)

よって以下でtrue。remember_token作るときに、新しく定義するのではなく、user.remember_tokenで紐付けしたほうがよかったな。

>> user.authenticated?(:activation, user.activation_token)
=> true

 

【11.3.2 editアクションで有効化 演習】

1. コンソールから、11.2.4で生成したメールに含まれているURLを調べてみてください。URL内のどこに有効化トークンが含まれているでしょうか?
→ 11.2.4の演習1と同じです。

 
2. 先ほど見つけたURLをブラウザに貼り付けて、そのユーザーの認証に成功し、有効化できることを確認してみましょう。また、有効化ステータスがtrueになっていることをコンソールから確認してみてください。
→ 認証成功、有効化できていました。

 

【11.3.3 有効化のテストとリファクタリング メモと演習】

・テストに出てくる配列deliveriesは変数。なので、他のテストに支障が出ないよう、setupでclearしている。
・sizeメソッド:lengthメソッドと同じ。ここではメールの数(=1)を確認している。
・assignsメソッド:対応するアクション内(ここではsignupをテストしているのでcreateアクション)のインスタンス変数(@user)にアクセスできるようになる。
・whereメソッド:与えられた条件にマッチするレコードをすべて返す。こちらにfind,find_byとの比較がありました。

1. リスト 11.35にあるactivateメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 11.39に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう (これでデータベースへの問い合わせが1回で済むようになります)。また、変更後にテストを実行し、 greenになることも確認してください。
→ 下記

user.rb
  def activate
    update_columns(activated: true, activated_at: Time.zone.now)
  end

 
2. 現在は、/usersのユーザーindexページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト 11.40のテンプレートを使って、この動作を変更してみましょう9 。なお、ここで使っているActive Recordのwhereメソッドについては、13.3.3でもう少し詳しく説明します。
→ 下記

users_controller.rb
  def index
    @users = User.where(activated: true).paginate(page: params[:page])
  end

  def show
    @user = User.find(params[:id])
    redirect_to root_url and return unless @user.activated?
  end

 
3. ここまでの演習課題で変更したコードをテストするために、/users と /users/:id の両方に対する統合テストを作成してみましょう。
訳注: updateメソッドは、コールバックとバリデーションを実行せずにスキップしますので、コールバックやバリデーションをかける必要がある場合は注意が必要です。
→ ここ難しいですね。発想としては、「有効化してないユーザー(@non_activated)がindexに表示されていない」のと、有効化してないユーザーのページ(user_path(@non_activated))にアクセスしようとすると、homeに飛ばされる」のを確かめればいいわけだけど…。前者のアサーションが分からず悩みました。
 結局調べたところ、「user_path(@non_activated)のリンクが表示されていない(0である)」のを確かめればいいと。なるほど。ということで下記です。テスト名もてきとうに追記しました。あと、setupに@non_activatedとして、uses.ymlの3番目のユーザーをactivated: falseに書き換えてから追加しています。

users_index_test.rb
  def setup
    @admin     = users(:michael)
    @non_admin = users(:archer)
    @non_activated = users(:lana)
  end

  test "index as admin including pagination and delete links, 
  not to show non activated user" do
    log_in_as(@admin)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    first_page_of_users = User.where(activated: true).paginate(page: 1)
    first_page_of_users.each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
      assert_select 'a[href=?]', user_path(@non_activated), count: 0
      unless user == @admin
        assert_select 'a[href=?]', user_path(user), text: 'delete'
      end
    end
    assert_difference 'User.count', -1 do
      delete user_path(@non_admin)
    end
    get user_path(@non_activated)
    assert_redirected_to root_url
  end

 

【11.4 本番環境でのメール送信 グチと演習】

 ついにきた!!SendGrid!!
これ酷くないですか?登録してもすぐにアカウント凍結されてメール飛ばせなくなるんですよ。前回こいつに時間取られた挙句、結局解決するにはサポートに連絡しないといけないらしく、やってられるかって放置しました。そんなもんチュートリアルに使うなよな…。
 ということで、別の手段を導入しようしていろいろ試したのですがどれもうまくいかず…。不服ですが今回はsendgridで済まします。これは今後の課題として置いておきます。
……結局そっこーで凍結されました。もうええわ。

1. 実際に本番環境でユーザー登録をしてみましょう。ユーザー登録時に入力したメールアドレスにメールは届きましたか?
→ うん、無理でした。

 
2. メールを受信できたら、実際にメールをクリックしてアカウントを有効化してみましょう。また、Heroku上のログを調べてみて、有効化に関するログがどうなっているのか調べてみてください。ヒント: ターミナルからheroku logsコマンドを実行してみましょう。
→ やりたいんやけどね、無理ですわ。

 

第11章まとめ

・終わり悪ければすべて悪い。SendGrid嫌い。
・今後は純粋にActionMailerでメール送信に取り組みたい。
・URLに有効化トークンとエスケープしたメールアドレスを盛り込む。

 
 SendGrid問題、なんとか解決したかったんやけどなあ…。これ以上時間かけるのももったいないので、次に進めます。今後絶対なんとかしてやる!
 さて、次は第12章、パスワードの再設定です。認証システム開発の最終章ですね!

 
⇨ 第12章へ!
⇦ 第10章はこちら
学習にあたっての前提・著者ステータスはこちら

 

なんとなくイメージを掴む用語集

・クエリパラメータ
 URLの末尾で疑問符「?」に続けてキーと値のペアを記述したもの。ユーザーがどこから来たのか解析する手段(パッシブパラメータ)や、指定された変数によってコンテンツの内容を変化させたりできる(アクティブパラメータ)。クエリのこと全般も含めて詳しくはこちら。

・CGI(Common Gateway Interface)
 クライアント側のWebブラウザの要求に応じてWebサーバが外部プログラムを呼び出して、その実行結果がHTTPを介してクライアントのWebブラウザに送信される仕組みのこと。掲示板、アクセスカウンター、アンケートフォームなどを実装できる。

・assert_mutch
 与えられた文字列が与えられた正規表現にマッチした場合、検査にパス。

・SMTP(Simple Mail Transfer Protocol)
 インターネットで電子メールを転送するプロトコル。

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

【Rails5】Railsで新規アプリを作る【初心者】

概要

『どうも皆さん、おはこんばんにちは』

(…一度言ってみたかった)

筆者は最近RoRアプリを作り始めた、思いっっきり初心者です。
Qiita初投稿という事で、めちゃくちゃ初歩的な「Railsで新規アプリを作る過程」について投稿したいと思います!:writing_hand:

前提条件

  • OS: MacOS
  • Rubyバージョン: 2.5.1
  • Railsバージョン: 5.2.3
  • データベース: MySQL
  • Rubyバージョン管理: rbenv
  • IDE: VisualStudioCode

上記の環境でアプリを作っていきます!:fist:

Railsで新規アプリを作る

ターミナル上で
rbenv local 2.5.1
rails _5.2.3_ new app_name --database=mysql --skip-bundle
を実行します。

何を行っているのか

  • rbenv rbenvコマンド実行の宣言
  • local 2.5.1 このプロジェクトで使用するRubyのバージョン指定(今回はver.5.2.3)
  • rails railsコマンド実行の宣言
  • _5.2.3_ rubyのバージョン指定(今回はver.5.2.3)
  • new 新しいアプリの作成コマンド
  • app_name 作りたいアプリの名前の定義
  • --database=mysql 使いたいデータベースの指定(今回はmysql)
  • --skip-bundle bundle installをスキップする指示
  • app_name .とすると、カレントディレクトリ上に作られる
  • --database=mysql は、-d mysqlでもOK:ok_hand:
  • --skip-bundle は、-BでもOK:ok_hand:

rbenvの使い方と仕組みについては、以下の記事がわかりやすいので参照してください。
rbenvの使い方と仕組みについて - Qiita

アプリ内のファイル

上記のようにターミナル上でrails newをすると、たくさんのフォルダやファイルが作られます。中身は以下のようになっているはずです。

app

アプリケーションのコントローラ、モデル、ビュー、ヘルパー、メイラー、チャンネル、ジョブズ、アセットが置かれています。

bin

アプリケーションを起動・アップデート・デプロイするためのRailsスクリプト等のスクリプトファイルが置かれています。

config

アプリケーションの設定ファイル (ルーティング、データベース等) がここに置かれています。

db

現時点のデータベーススキーマと、データベースマイグレーションファイルが置かれています。

lib

アプリケーションで使う拡張モジュールが置かれています。

log

アプリケーションのログファイルが置かれています。

public

このフォルダの下にあるファイルは外部 (インターネット) からそのまま参照できます。静的なファイルやコンパイル済みアセットをここに置きます。

storage

Diskサービスで用いるActive Storageファイルが置かれています。

test

Unitテスト、フィクスチャなどのテスト関連ファイルをここに置きます。

tmp

キャッシュ、pidなどの一時ファイルが置かれます。

vendor

サードパーティによって書かれたコードはすべてここに置きます。通常のRailsアプリケーションの場合、外部からのgemファイルをここに置きます。

.gitignore

Gitに登録しないファイル(またはパターン)をこのファイルで指定します。

.ruby-version

デフォルトのRubyバージョンがこのファイルで指定されています。

config.ru

アプリケーションの起動に必要となる、Rackベースのサーバー用のRack設定ファイルです。

Gemfile

Railsアプリケーションで必要となるgemの依存関係を記述します。このファイルはBundler gemで使われます。

package.json

Railsアプリケーションで必要なnpm依存関係をこのファイルで指定できます。このファイルはYarnで使われます。

Rakefile

このファイルには、コマンドラインから実行できるタスクを記述します。ここでのタスク定義は、Rails全体のコンポーネントに対して定義されます。

独自のRakeタスクを定義したい場合は、Rakefileに直接書くと権限が強すぎるので、なるべくlib/tasksフォルダの下にRake用のファイルを追加するようにしましょう。

README.md

アプリケーションの概要を説明するマニュアルをここに記入します。このファイルにはアプリケーションの設定方法などを記入し、これさえ読めば誰でもアプリケーションを構築できるようにしておく必要があります。

gemのインストール

Rubyにおけるgemは、下記2つの役割を持ちます。

  1. パッケージ
  2. パッケージ管理ツール

パッケージを使うことで開発を効率的に進めることができるので、実際の現場でも使用されることが多いようです。
簡単にパッケージをインストール可能なので、Ruby on Rails 初心者でも素早く本格的なアプリ機能を装備することができるんですね。:relaxed:

アプリ内で使いたい機能を持つgemを Gemfileに、

Gemfile
gem 'gem名'

のように記述します。(ここでは、具体的なgemの種類については割愛します。)

また、Gemfile内のrailsの記述を、

Gemfile
~
gem 'rails', '5.2.3'
~

のように使いたいバージョン(今回はver.5.2.3)で固定します。

先ほど--skip-bundleコマンドでgemのインストールをスキップしたので、ターミナル上で
bundle install
を実行します。

何を行っているのか

  • bundle install bundler というgemを使って、Gemfileの記載内容に従ってgemをインストールするためのコマンド

サーバーの立ち上げ

実際にサーバーを立ち上げてみましょう!ターミナル上で
rails server
を実行します。

何を行っているのか

  • rails server rails newコマンド で作ったRailsアプリケーションを、local環境でサーバーに繋ぐ指示
  • rails server は、rails s でもOK:ok_hand:

ターミナル上で以下のようなログが流れればサーバーとの接続が完了です!

=> Booting Puma
=> Rails 5.2.3 application starting in development 
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.6 (ruby 2.5.1-p57), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

ブラウザ上で確認してみましょう!
localhost:3000 にアクセスします。

Yay!

無事にサーバーへ接続できました!:sunglasses:

終わりに

最後までご覧いただきありがとうございました。
今後はdeviseでのユーザー登録方法、Docker上でのRoR環境構築なども記事にしたいなあと考えています(いつになるかは未定)。

また、この記事に関して不明な点等ありましたら、お知らせくださると幸いです。
早くエンジニアとして「駆け出したい」ですね。:walking_tone1::walking_tone1::walking_tone1:
それでは!

参考にしたWebサイト

Railsガイド
CodeCampus

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

2桁以上の数字から、任意の桁の数値を取りたい時の方法!!

こんばんは!!
アロハな男、やすのりです:kissing_smiling_eyes:

今週勉強している中で理解するのに少し時間が必要だったことについて備忘録も兼ねて書き記しておこうと思います:smirk:

今回のテーマは題名の通り『2桁以上の数字から、欲しい桁の数値を取る時はどうしたらいいの!?:sob:を解決しちゃいましょう!!

ちなみに、計算は全て整数になっているので今回は省きますが、小数点が入っていたりしたらto_iメソッドを使ったりと少し変化も有ります:wink:

どうやって欲しい桁の数値を取るの?

それじゃあ、まず先に百の位までの数値の取り方をずらっと書いちゃいますね!

num = 123

# 100の桁が欲しい場合
digit_100 = (num / 100) % 10

# 10の桁が欲しい場合
digit_10 = (num / 10) % 10

# 1の桁が欲しい場合
digit_1 = (num / 1) % 10

puts "100の桁の数値は#{digit_100}です"
puts "10の桁の数値は#{digit_10}です"
puts "1の桁の数値は#{digit_1}です"

さぁ、これでコードの記述が終わりましたね!!
これを実行するとちゃんと欲しい桁の値が取れているのか...:sweat:

結果

100の桁の数値は1です
10の桁の数値は2です
1の桁の数値は3です

ちゃんと出力されましたね!!:grin:

それじゃあ細かく見ていきましょう。

解説

それじゃあ10の桁の場合を見ていきましょう

digit_10 = (num / 10) % 10

となっていますね。
まず数式の前半部分を計算すると12が返ってきます。
ここから元々10の桁だった部分が、今は1の桁に移動していますね。
あとは10の桁以上の数値が不要になるわけなので計算して1の桁だけが欲しいところ...:thinking:

そうか!!
10で割って1の桁を余りとして返せばいいのか!!ということで後半部分の『%』の出番ですね:grin:

12を10で割った余りを出力結果とするから

2

が結果として返ってくるわけですね!!

この前半部分の10というところ、今回は10の桁だったので10で割ってますが、1万の桁なら1万で、100万なら100万の桁で割れば欲しい桁の数値が1の桁に返ってきます!!

なぜ理解するのに時間が必要だった?

今回の説明を見れば『ん?意外と簡単じゃない?なんで理解に時間がかかったの?:thinking:』と思う方もいらっしゃるかもしれません。

実は今回の桁数取得の方法を考えている時に出されていた問題が『2桁の数字から数値をそれぞれ取得しなさい』という旨の問題だったんです!!

先ほどの解説の時にも説明しましたが、1の桁を欲しいのであれば『10で割った余りを返り値とすればいいだけじゃん』となったんです。
そして10の桁も『10で割った答えだけで桁の数値が取れるじゃん』となりました。

そうするとどうでしょう

digit_10 = num / 10
digit_1 = num % 10

というコードになっちゃいました:joy:
これでも2桁だけなら答えは一緒なので『あれ...なんでこれでダメなんだ?』となってしまいました...

このコードだと桁数が増えた時に、1の桁と1番上の桁の数値を取得するだけなら対応できますが、中間の桁を取得することができません!!

そのことに気付いたら理解がすぐにできましたが、それに気付くまでに少し時間がかかってしまいました:sweat_smile:

結論

こんな簡単そうに見えることでもハマると一切理解ができない。
だが理解できた時の達成感は変えがたいものが有ります。
もう脳汁ドパドパでてる感じでした:laughing:

皆さんも困難にぶつかっても諦めずレッツトライ!!

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

【Rails】deviseの会員登録に確認画面と完了画面を加える。

環境

Ruby 2.5.7
Rails 5.2.4

gem

gem 'devise'

前提

deviseの新規登録フォームがカスタムできる状態(デフォルトではない状態)
※事前に、新規登録フォームがカスタムでき問題なく動作することをご確認ください。

やりたいこと

deviseのデフォルト設定ではsign_upページで情報が入力され送信されると、そのまま登録されると共にあらかじめ指定した(されている)ページに遷移します。
登録の前に入力された情報が確認できるページを挟んだり、登録が完了したことをお知らせするページがあると、より親切かと思いますので、今回は「入力フォーム」に加えて、「入力された情報の確認画面」、「登録完了画面」を作成していきます。
今回の作成で完成するページの流れとしては、
新規会員情報の入力フォームusers/new.html.erb(新規作成)→入力情報の確認画面users/registrations/new.html.erb→登録完了画面users/completion(新規作成)という順番で画面が遷移していきます。

少し違和感があるかもしれませんが、deviseで用意されているusers/registrations/new.html.erbは前のページで入力された情報の確認のみで、見た目上はその前のページで入力された値を表示するだけになります。

手順

1.views/usersに入力画面(new.html.erb)、完了画面(completion.html.erb)を用意
2.controllers(controllers/users/registrations_controller.rb)に、会員登録後のリダイレクト先(users/completion.html.erb)を追記
3.routes.rbに、1.で追加したviewファイルのルーティング、registrations_controller.rbの使用を追記
4.(controllers/users_controller.rb)にrender用newとcompletionを作成

1.確認画面、完了画面の作成

冒頭で記した通り、新規登録時の画面の流れとしては、
新規登録入力フォームusers/new.html.erb→入力確認画面users/registrations/new.html.erb→登録完了画面users/completionとなります。
確認画面で"登録する"ボタンを押すとdeviseの標準機能でユーザー情報が登録されます。

入力画面

users/new.html.erb
<div>

  <h2>新規会員登録</h2>

  <%= form_with url: new_user_registration_path, method: :get, local: true do |f| %>

    <%= f.label :name %>
    <%= f.text_field :name %>

    <%= f.label :phone_number %>
    <%= f.number_field :phone_number %>

    <%= f.label :email %>
    <%= f.email_field :email %>

    <%= f.label :password %>
    <% if @minimum_password_length %>
      (<%= @minimum_password_length %>文字以上)
    <% end %>

    <%= f.password_field :password %>
    <%= f.password_field :password_confirmation %>

    <%= render "users/shared/links" %>

    <%= f.submit "確認" %>
  <% end %>
</div>

元々用意されているregistrations/new.html.erbのフォームを参考に新規作成します。
登録情報に名前や電話番号情報を追加する方法は割愛します。
追加される場合は先にそちらの設定をして動作が確認できた上で、今回の方法を試していただくか、カラムは増やさずにデフォルトの設定(メールアドレス、パスワードのみ)でお試しください。

ポイントとしてはフォームタグです。

users/new.html.erb
  ...

  <%= form_with url: new_user_registration_path, method: :get, local: true do |f| %>

    ...

  <% end %>

ここでは保存するモデルは指定せず、urlだけを指定しています。
つまり、フォーム内でsubmitされると、ここで指定したurlに遷移され、フォームに入力されている値も"パラメータ"として遷移先に渡されます。
ここで指定するurlは次の確認画面のurl: new_user_registration_pathを指定します。
このurlはdeviseがデフォルトで用意しているregistrations/new.html.erbです。

このフォームはurlを遷移するだけなので、HTTPメソッドは"POST"ではなく"GET"を指定します。
フォームのHTTPメソッドはデフォルトが"POST"になっているので、method: :getを明示する必要があります。

今回使うフォームタグはform_withです。
form_withはデフォルトの送信方法がAjax(remote: true)で、画面遷移が行われない仕様なので、local: trueも明示する必要があります。
form_forの場合はデフォルトがlocal: trueなので、普段は意識する必要はありません。

確認画面

users/registration/new.html.erb
<div>

  <h2>入力情報の確認</h2>

  <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>

    <p><%= params[:name] %></p>

    <p><%= params[:phone_number] %></p>

    <p><%= params[:email] %></p>

    <% unless params[:password].nil? %>
      <%
        i = 0
        password = ""
        while i < params[:password].length
          password << "*"
          i += 1
        end
      %>
    <% end %>
    <p><%= password %></p>

    <%# 実際に送信するパラメータ %>
    <%= f.hidden_field :name_family, value: params[:name] %>
    <%= f.hidden_field :phone_number, value: params[:phone_number] %>
    <%= f.hidden_field :email, value: params[:email] %>
    <%= f.hidden_field :password, value: params[:password] %>

    <%= f.submit "登録する" %>

  <% end %>
</div>

この画面がdeviseで用意されているusers/registrations/new.html.erbです。
元々のこのページには入力フォームがありますが、今回は確認だけなので、フォームは表示しません。
form_forから始まるフォームタグは元からあるものをそのまま利用しています。

前のページから渡されてるパラメータをparams[:name]などを用いて取得し、確認できるように表示します。

パスワード部分については入力されたものが何なのかわからない状態にしています。
(パラメータには載っているのですが、ここの隠し方がわからなかったので、詳しい方ご教授いただけると幸いです。)

users/registrations/new.html.erb
<%
  i = 0
  password = ""
  while i < params[:password].length
    password << "*"
    i += 1
  end
%>

params[:password].lengthでパスワードの文字数を取得しています。
while i < params[:password].length ... endで処理の中でiが1ずつカウントアップしていきます。
同時に、直前で定義した変数password = """*"が足されていき、文字数に達したら処理が終了します。
<%= password %>変数の中身を表示します。

users/registration/new.html.erb
<%= f.hidden_field :name_family, value: params[:name] %>
...

f.hidden_fieldは表示はされませんが、そこに指定されているvalue: params[:name]などはフォームとして送信されます。
今回のフォームは保存するモデルも(resourceでdeviseが自動的に)指定されているので、このf.hidden_fieldで指定された値はそのまま保存される値になります。

完了画面

users/completion.html.erb
<div>
  <% if request.referrer.include?('users/sign_up') %>
    <p>登録が完了しました!</p>
  <% else %>
    <p>既に登録済みです。</p>
  <% end %>
</div>

前のページで登録するが押されると、確認した会員情報が保存されると同時に、この画面に遷移します。

request.referrerを使うと、どのページからここに遷移してきたか、前のページのURLが取得できます。
request.referrer.include?('users/sign_up')のinclude?メソッドは()の中の値がそこに含まれているかどうかを返すメソッドです。
ここでusers/sign_upが前のページのURLに含まれていたかどうかを判断して、そのページで表示するテキストを変えています。
users/registrations/new.html.erb以外からの遷移であれば、登録済みと表示され、そもそも未登録(未ログイン)の場合に表示しようとするとdeviseのbefore_action :authenticate_user!で、他のページにリダイレクトが行われます。

2.users_controller.rbにnewとcompletionを追加

前の手順ではviewファイルを作成しましたが、このままでは画面表示ができないので、コントローラーでアクションを記述する必要があります。

users_controller.rb
class UsersController < ApplicationController

  before_action :authenticate_user!, except: %i[new]

  ...

  def new
  end

  def completion
  end

  ...

end

newページは新規会員登録フォームなので、下記のように記述する必要があります。
before_action :authenticate_user!, except: %i[new]
before_action :authenticate_user!, except: [:new]
before_action :authenticate_user!, except: :new
どれも意味は一緒ですが、記述するアクションが増えた時に、よりコードが短くなるのは1行目のものです。

ちなみに%iで複数指定するときはbefore_action :authenticate_user!, except: %i[new action1 action2 action3]という感じでスペースで区切るだけです。

completionは未ログインの場合はリダイレクトさせたいのでexceptに含めません。

3.新たに作成したviewとusers/registrations_controller.rbをroutes.rbに記述する

routes.rb
Rails.application.routes.draw do

  ...

  # registrations_controller.rbを有効にします。
  devise_for :users, controllers: {
    ...
    registrations: 'public/users/registrations'
  }

  # 作成したusers/new.html.erbとusers/completion.html.erbをルーティングに追加します。
  resources :users, only: %i[new] do
    get 'completion', to: 'users#completion'
  end

end

4.登録後のリダイレクト先を変更

確認画面で登録するが押されたときに、前の手順で用意したusers/completion.html.erbに遷移させる必要があります。

users/registrations_controller.rb
class Public::Users::RegistrationsController < Devise::RegistrationsController

  ...

  def after_sign_up_path_for(resource)
    public_user_completion_path(resource)
  end

  ...

end

デフォルトか、もしくは任意で別のページがリダイレクト先になっているかと思いますが、そのpathを前の手順で作成したpublic_user_completion_path(resource)(users/completion.html.erbのpath)に設定します。

まとめ

より実用的な使い方については、私のGitHubに実際に使っているファイルを公開しているのでそちらも参考にしていただければと思います!
GitHub - MasaoSasaki/matchi

質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。

最後まで読んでいただきありがとうございました。

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

【Active Storage】ファイルアップロード時のバリデーション設定

概要

Active Storageにはデフォルトのバリデーションがないため、自前でバリデーション設定をした時のことを備忘録として記録します。

環境

・ruby '2.5.7'
・rails '5.2.3'

過程

1.準備

Active Storageを導入するために以下のコマンドを実行します。

$ rails active_storage:install

以下の2つのテーブルを作成するマイグレーションファイルが作成されます。
・active_storage_attachments
・active_storage_blobs

rails db:migrateしてテーブルを作成します。

$ rails db:migrate

2.モデルへの関連付け

モデルへの関連付けをしていきます。
ここでは、Userモデルに複数のプロフィール画像を設定する場合とします。

models/user.rb
class User < ApplicationRecord
  has_many_attached :avatars
end

:avatarsはファイルの呼び名で、:photos:imagesなどファイルの用途に合わせて好きなものが指定出来ます。Userモデルに画像用のカラムを用意する必要はありません。

3.バリデーションの設定

Userモデルにバリデーションの設定をしていきます。
ここでは、次の3つの設定をしていきます。

・avatar_type
アップロード出来るファイル形式を指定します。

・avatar_size
アップロード出来る1ファイルの大きさ(容量)を指定します。

・avatar_length
アップロード出来るファイルの数を指定します。

models/user.rb
class User < ApplicationRecord
(省略)
  validate :avatar_type, :avatar_size, :avatar_length

  private

  def avatar_type
    avatars.each do |avatar|
      if !avatar.blob.content_type.in?(%('image/jpeg image/png'))
        avatar.purge
        errors.add(:avatars, 'はjpegまたはpng形式でアップロードしてください')
      end
    end
  end

  def avatar_size
    avatars.each do |avatar|
      if avatar.blob.byte_size > 5.megabytes
        avatar.purge
        errors.add(:avatars, "は1つのファイル5MB以内にしてください")
      end
    end
  end

  def avatar_length
    if avatars.length > 4
      avatars.purge
      errors.add(:avatars, "は4枚以内にしてください")
    end
  end
end

ちなみに、avatars.purgeで一時データを削除しています。
他の入力項目でバリデーションエラーが起きてしまうと、Rails5.2系〜6.0系以前ではモデルのインスタンスのattributeに代入した時点で、アップロードしたファイルがストレージに保存されてしまい、その結果として、blobにエラー用の一時データが溜まってしまう可能性があるためです。
(ただ、Rails6.0へのアップデートでActive Storageの機能変更があり、saveメソッドが実行された後に、ストレージに保存される仕様に変更されたので、6.0以降であれば不要です)

結果

これで、次のようにバリデーションの設定が出来ました!
200919_バリデーションエラー.png

参考

ActiveStorageがCarrierWaveの代用として使えるか考える
Rails5 Active Storageを使って画像アップロード機能を実装する

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

Ruby のメソッド呼び出しと変数参照について注意すること

ブログ記事からの転載です。

最近、ハマっている人が何人書いたのでちょっとまとめてみます。

Ruby のメソッド呼び出し

Ruby ではメソッドを呼び出す場合に他の言語と比較して『() を省略してメソッドを呼び出す事』ができます。

def hoge(a = nil)
  "#hoge(#{a})"
end

# 括弧がなくてもメソッドを呼び出せる
p hoge
# => "#hoge()"

# メソッドっぽい呼び出しでもメソッドを参照できる
p hoge()      # => "#hoge()"
p hoge 42     # => "#hoge(42)"
p self.hoge   # => "#hoge()"

まあこれはそのとおりですね。

メソッド名と同名の変数名が定義されていたらどうなるの

問題は『同名のメソッドと変数』が混載している場合です。
この場合は『変数が参照できれば変数』を参照し、『そうでなければ』メソッドを参照します。

def hoge(a = nil)
  "#hoge(#{a})"
end

# この時点では変数が定義されていないのでメソッドを優先して呼び出す
p hoge   # => #hoge()

# 変数を定義する
hoge = 42

# 変数を定義したあとでは変数を優先して定義する
p hoge   # => 42

# メソッドっぽい呼び出しではメソッドを呼び出す
p hoge()      # => "#hoge()"
p hoge 42     # => "#hoge(42)"
p self.hoge   # => "#hoge()"

この時に注意するのは『代入式よりも前であればメソッド』を参照し、『代入式よりもあとであれば変数』を参照します。

変数が定義されるタイミングは?

例えば次のように『実際に変数を定義している処理が呼ばれないケース』が Ruby では存在します。

def hoge(a = nil)
  "#hoge(#{a})"
end

if false
  hoge = 42
end

# これはメソッド参照?
p hoge

これはインタプリタ言語的には『変数が定義されていないのでメソッドが呼び出される』ことを期待する方もおられるかもしれません。
しかし、実際には p hoge では『変数 hoge 』を参照します。
これは Ruby がソースコードを実行する仕組みに秘密があります。
Ruby ではソースコードを実行する前にまず『全 Ruby のソースコードをパースしてから』Ruby のコードを実行します。
なので、上のコードのように『実行時にそのコードが呼び出されるかどうか』というのは関係なく全ソースコードがパースされるので『代入式』が定義された時点で hoge という変数が暗黙的に定義されたことになります。
なので、実際に if 文の中身が呼ばれるかどうか関係なく『代入式』が定義された時点で『if 文の外』でも『変数 hoge』が参照されるようになります。

# 実際の実行結果
def hoge(a = nil)
  "#hoge(#{a})"
end

if false
  hoge = 42
end

# ここでは変数を参照する
# 変数のデフォルト値は nil なので nil を返す
p hoge
# nil

後置if + 変数定義

Ruby がどのようにソースコードをパースするのかを理解するのはとてもむずかしいです。
例えば次のように『後置if で hoge を参照しつつ hoge 変数を定義する』場合どうなるでしょうか。

hoge = 42 if hoge.nil?
p hoge

これは if hoge.nil? が変数定義よりも前に処理されるので実際には hoge = 42 という処理は呼び出されない、と考える人も多いと思います。
しかし、実際には hoge = 42 if hoge.nil? というコードは『これ全体で1つの処理』としてパースされます。
なので、 if hoge.nil? が呼び出された時点ですでに hoge = 42 というコードはパース済みになっており『変数 hoge は定義されている』という処理になります。
なので、先程のコードの実行結果は、

hoge = 42 if hoge.nil?
p hoge
# => 42

と、いう風に『 hoge = 42 』が処理された状態になります。
逆に後置 if でない場合は結果が異なるので注意する必要があります。

# これはエラーになる
# error: undefined local variable or method `hoge' for main:Object (NameError)
if hoge.nil?
  hoge = 42
end

これは if hoge.nil? が変数よりも前にパースされて『変数が定義されていない状態』で if 文の条件式が実行されるためです。

eval("hoge") するとどうなる

Ruby では eval というメソッドが存在します。
これは『実行時』に Ruby のソースコードを実行するメソッドです。

def hoge(a = nil)
  "#hoge(#{a})"
end

hoge = 42
# eval に渡した文字列を Ruby のコードとして実行する
p eval("hoge + hoge")
# => 84

上のコードを実行すると代入式よりもあとで `"hoge + hoge" を実行しているので『変数』を参照します。
では、次のようなコードを実行するとどうなるでしょうか。

def hoge(a = nil)
  "#hoge(#{a})"
end

# 代入式よりも前に hoge を実行する
p eval("hoge")

hoge = 42

先程から説明している流れからいうと『変数よりも前に "hoge" を実行している』のでこれは『メソッド呼び出し』になることを期待します。
しかし、実際に実行すると以下のような結果になります。

def hoge(a = nil)
  "#hoge(#{a})"
end

# メソッド呼び出しではなくて変数を参照する
p eval("hoge")
# => nil

hoge = 42

なぜ、このような結果になるのかというと『 Ruby のソースコードするタイミング』と『 eval を実行するタイミング』た異なる為です。
eval を実行するタイミングはあくまでも『Ruby の実行時』になります。
この『実行時』というのは『Ruby のソースコードがパースされたあと』になります。
つまり『 eval を実行するタイミング』ではすでに『Ruby のソースコードがパースされたあと』になっているため hoge という式は『変数を優先して』参照してしまうのです。
なので eval から変数やメソッドを参照する場合、『代入式の定義位置』に関係なく『変数』を優先して呼び出されるのです。
これは binding.irb を使用したときにも影響し、例えば次のように『代入式よりも前』で binding.irb を呼び出した時に問題になります。

def hoge(a = nil)
  "#hoge(#{a})"
end

# デバッグ等で binding.irb で実行時に irb を起動する
# この irb のコンソール上で hoge を参照すると『変数』を参照する
binding.irb

hoge = 42

binding.irb では入力した Ruby のコードを eval を用いて実行します。
なので、先程の例のように『変数』を参照して実行されることになります。
普段の Ruby のコードでは eval を使うととはめったにないと思うんですがこのように binding.irb などを使用すると間接的に eval を使うことになるので注意する必要があります。

まとめ

  1. Ruby では『 hoge 』という式がメソッド呼び出しなのか変数参照なのか曖昧である
  2. 変数が存在している場合は変数を優先し、そうでない場合はメソッド呼び出しを優先する
    • 代入式より前はメソッド呼び出し、それよりあとは変数参照になる
  3. ただし、動的に変数を参照する場合は変数を優先するので注意する
    • evalbinding.irb を使う場合は注意する
  4. 基本的にはメソッド名と同じ名前の変数名は避けるべきではある
    • 避けるべきではあるが実際に『どういうメソッド』が定義されているのか不透明なのでむずかしい 
  5. Ruby ではそれが『変数』なのか『メソッド』なのかを意識でコードを書くことが重要

と、言う感じで Ruby の変数についてまとめてみました。
実際に binding.irb でメソッドを呼び出した場合に nil が返ってくる事があり、よくよくコードを見てみたら binding.irb よりもあとで同名の変数名が定義されている事がありました。
このように Ruby ではハマりポイントがあるので注意しましょう。

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

テスト

テストです

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

タイトルをここに入力

Rubyについて

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

【Ruby on Rails】投稿点数ランキング機能(全体表示)

目標

  • ユーザーの投稿点数ランキング
    • 投稿に対して評価点数を合計/平均して、その投稿ユーザーの投稿点数ランキング
  • 投稿の点数ランキング(user部分を投稿に変更したら可能です)
    • 投稿に対して評価点数を合計/平均して、その投稿の点数ランキング

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

前提

※ ▶◯◯ を選択すると、説明等が出てきますので、
  よくわからない場合の参考にしていただければと思います。

※数値がまだないユーザーも表示できるよう、
left_joinsとsort_byを使って、ランキング機能を実装します。
※他のランキング方法もあるため、随時投稿していきます。

カラム追加

投稿に対するコメントにて、評価をつけるため追加。

ターミナル
$ rails g migration AddScoreToComments score:integer
db/migrate/xxxxxxxxxxxxx_add_score_to_comments.rb
class AddScoreToComments < ActiveRecord::Migration[5.2]
  def change
    add_column :comments, :score, :integer, default: 0
  end
end

このままでは点数に差が生まれてきてしまうので、
viewで点数を選択式にできるように記述。

app/views/posts/show.html.erb
評価:<%= f.number_field :score,min:1,max:5 %>
合計値を出す場合
<% @sum = 0 %>
<% @post.comments.each do |comment| %>
  <% @sum += comment.score %>
<% end %>
<%= @sum %>
平均値を出す場合
<% @average = 0 %>
<% @post.comments.each do |comment| %>
  <% @average += (comment.score / @user.comments.count) %>
<% end %>
<%= @average %>

準備が出来たので、本題に進みます。

controller

コントローラーが肝になります。

合計値のランキング:app/controllers/users_controller.rb
  def rank
    @users = User.
              left_joins(:comments).
              distinct.
              sort_by do |user|
                hoges = user.comments
                if hoges.present?
                  hoges.map(&:score).sum
                else
                  0
                end
              end.
              reverse
  end
平均値のランキング:app/controllers/users_controller.rb
  def rank
    @users = User.
              left_joins(:comments).
              distinct.
              sort_by do |user|
                hoges = user.comments
                if hoges.present?
                  hoges.map(&:score).sum / hoges.size
                else
                  0
                end
              end.
              reverse
  end

補足
・left_joins(:comments):左外部結合と言って、コメントに紐づくuserを全て取得し、
 コメントがない場合はnullで取得するメソッドです。こちらの解説がわかりやすいです。
 【Rails】left_joinsメソッドで定義する左外部結合とは?
・distinct:重複レコードを1つにまとめるためのメソッドであり、
 今回の場合はコメントが2つ以上あると、userも2つ以上取得してしまうため、
 重複を回避するために使用します。
・sort_by do |user|:配列を小さい順に並び替えるメソッドです。
 値としてはif以降の条件で代入していきます。
・map(&:score):scoreの値だけ、追加していくメソッドです。
 こちらの解説がわかりやすいです。
 Railsでよく見る arr.map(&:id) の意味
・reverse:sort_byで小さい順に並び替えたため、逆にしています。
 ただし、同数値の場合、新しい順になるため、注意が必要です。
 ここは解決出来ましたら再度更新します。
・hoges.size:hogesの数を取得しています。その数値を元に平均を出しています。

view

下記のようにeach降順順で表示可能
app/views/users/rank.html.erbを作成後、

app/views/users/rank.html.erb
<% @users.each do |user| %>
  <%= link_to user_path(user) do %>
    <%= user.name %><br>
  <% end %>
<% end %>

routing

config/routes.rb
get '/rank', to: 'users#rank'

参考サイト

Railsでお手軽ランキング機能
【Rails】ランキング機能の実装

まとめ

今回は高いスコアを投稿しているユーザーを表示したかったため、
このような記述となりましたが、
あまり実用的ではないかもしれません。
ただ、これらのランキングを使えば、
点数を高く評価している人だけ、または低い人だけに
アプローチを取ることができるため、営業向きのランキングかと思います。

また投稿の点数ランキングは口コミランキングなどを実装したい場合は、
実用的な記述になるかと思います。

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

未経験エンジニア転職中にで出たコードディングテスト問題

未経験転職にて出題された問題と、自分なりに調べて処理した答えを備忘録として載せます。

実力不足の自分なりに出した答えなので、修正や解説をいただけるととっても嬉しいです!

▲問題1 FizzBuzz

1から100までの数字を順番に出力する関数を作成してください。
ただし、出力する数字が3の倍数の場合には「Fizz」を、出力する数字が5の倍数の場合には「Buzz」を、出力 する数字が3の倍数かつ5の倍数の場合には「FizzBuzz」を、それぞれ数字の代わりに出力してください。
また、各数字もしくは文字列の出力の後に改行コード(LF - \n)を出力してください。

fizzbuzzのコード
(1..100).each do |n| # 1〜100まで処理を繰り返す
  def fizzbuzz(n)
    if n % 15 == 0 # 3と5の倍数(3✖︎5)の時にFizzBuzzを出力
      puts "FizzBuzz"
    elsif n % 3 == 0 # 3の倍数の時にFizzを出力
      puts "Fizz"
    elsif n % 5 == 0 # 5の倍数の時にBuzzを出力
      puts "Buzz"
    else
      puts n # 上記の条件に当てはまらない場合に数字を出力
    end
  end

  puts fizzbuzz(n) # fizzbuzzメソッドを出力
end

▼問題2 素数を出力

1から1000までの素数を出力する関数を作成してください。
素数の定義は下記の通りである。

素数とは、1より大きい自然数で、約数が1と自分自身のみである数字である。

素数を出力するコード
primeRetrun = [] # primeReturnを配列にする
(1..1000).each do |n| # 1〜1000まで繰り返し処理
  next if n == 1 # ifの条件がtrueの場合、nextで次の処理に移る

  if n == 2
    primeRetrun.push(n) # pushメソッドでprimeReturnの配列の中にnを加える
    next # 次の処理に移る
  end

  judge = true
  primeRetrun.each do |number|
    if n % number == 0 # 素数の処理
      judge = false
      break # 強制的に今のeach処理を終わらせて最初のeach処理に戻る
    end
  end

  primeRetrun.push(n) if judge # judgeがtrueだった場合のみ、pushメソッドでprimeReturnの配列の中にnを加える
end

puts primeRetrun # 配列の処理を加えて入れる

※rubyの場合もっと簡単にできます。

rubyの簡単コード
require 'prime' # primeライブラリを読み込み
Prime.each(1000) {|x| p x} # あらまぁとっても簡単。。。

▼問題3

0から9999までの数字を順番に1刻みで出力するプログラムを作成しました。 このプログラムを実行した際に、数字の7は何回出力されますか? 例えば、7777が出力された場合、数字の7は4回出力されたものとしてカウントします。 この問題の答え、もしくは答えを計算するためのプログラムを解答してください。

素数を出力するコード
num = [*(1..9999)].to_s # to_s配列の中身を文字列に変換

puts num.count("7") # 配列の中身の文字列の中から7の文字数をカウントする

参考にしたURL

https://qiita.com/motoki4917/items/ffc89d955e20b91d1014
https://techacademy.jp/magazine/7507
https://docs.ruby-lang.org/ja/latest/class/Prime.html
https://docs.ruby-lang.org/ja/latest/method/Array/i/inspect.html
https://qiita.com/syo19961113/items/9f189424b5af5e084d33

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

[Visual Studio Code] rbenv 使用時のデバッグ実行で syntax error が出る

環境

項目 バージョン
OS Mac Catalina 10.15.6
Visual Studio Code 1.49.0
Ruby 2.7.1

Rubyのバージョン管理として、 rbenv を使用していた

前提

デバッグに必要な gem は Gemfile に追加しており、インストール済みの状態だった

gem "ruby-debug-ide"
gem "debase"

エラー

Visual Studio Code 左側の Run を選択し、 [Add Config] -> [RSpec - active spec file only] を選ぶ

上記の設定でデバッグしたところ、画面下のステータスバーが実行中を示すオレンジ色のまま、何も進まない状態になった

ReRunすると下記のように [OUTPUT] ビューに以下のエラーが出力される

Uncaught exception: /Users/pldb/.rbenv/shims/rspec:3: syntax error, unexpected tSTRING_BEG, expecting do or '{' or '('
[ -n "$RBENV_DEBUG" ] && set -x
     ^
/Users/pldb/.rbenv/shims/rspec:3: syntax error, unexpected ']', expecting end-of-input
[ -n "$RBENV_DEBUG" ] && set -x
                    ^

/Users/pldb/.rbenv/versions/2.6.6/bin/rdebug-ide:23:in `load': /Users/pldb/.rbenv/shims/rspec:3: syntax error, unexpected tSTRING_BEG, expecting do or '{' or '(' (SyntaxError)
[ -n "$RBENV_DEBUG" ] && set -x
     ^
/Users/pldb/.rbenv/shims/rspec:3: syntax error, unexpected ']', expecting end-of-input
[ -n "$RBENV_DEBUG" ] && set -x
                    ^
    from /Users/pldb/.rbenv/versions/2.6.6/bin/rdebug-ide:23:in `<main>'

結果

.vscode/launch.json を以下のように変更した

変更前

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "RSpec - active spec file only",
      "type": "Ruby",
      "request": "launch",
      "program": "/Users/pldb/.rbenv/shims/rspec",
      "args": [
        "-I",
        "${workspaceRoot}",
        "${file}"
      ]
    }
  ]
}

変更後

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "RSpec - active spec file only",
      "type": "Ruby",
      "request": "launch",
      "program": "${workspaceRoot}/bin/rspec",
      "args": [
        "-I",
        "${workspaceRoot}",
        "${file}"
      ]
    }
  ]
}

これは Visual Studio Code のデフォルト設定そのままである
ただし、binstub を使用した

原因

launch.json を生成した時点の "program""${workspaceRoot}/bin/rspec" だった

ところが使用しているrspecのパスが正しいものと考えて以下のパスに差し替えた

% which rspec
/Users/pldb/.rbenv/shims/rspec

表題のエラーはこれによって発生した

参考: rspec doesn't execute as a program, it is parsed as if it's a code file.

実際に参照していた shims配下のrspec の中身はこれである

#!/usr/bin/env bash
set -e
[ -n "$RBENV_DEBUG" ] && set -x

program="${0##*/}"
if [ "$program" = "ruby" ]; then
  for arg; do
    case "$arg" in
    -e* | -- ) break ;;
    */* )
      if [ -f "$arg" ]; then
        export RBENV_DIR="${arg%/*}"
        break
      fi
      ;;
    esac
  done
fi

export RBENV_ROOT="/Users/pldb/.rbenv"
exec "/usr/local/Cellar/rbenv/1.1.2/libexec/rbenv" exec "$program" "$@"

これはrbenvが生成した(rspecを参照するための)bashファイルであり、これをそのまま読み込んだ結果エラーになっていた
つまり rspec を十分に理解していないことが原因であった

対策

binstub を使用する

参考: 【翻訳+解説】binstubをしっかり理解する: RubyGems、rbenv、bundlerの挙動

プロジェクト内で使用するrspecにするために、 binstub で ${workspaceRoot}/bin/rspec を生成する

${workspaceRoot} はVisual Studio Code で展開したディレクトリのフォルダを指す

このディレクトリまで移動してから以下を実行する

% bundle binstubs rspec-core
% ./bin/rspec -v
RSpec 3.9
  - rspec-core 3.9.2
  - rspec-expectations 3.9.2
  - rspec-mocks 3.9.1
  - rspec-support 3.9.3

これでデフォルトの設定のまま実行できる

gem を作成している場合は、(他コントリビュータに影響しないよう)binフォルダを避ける

% bundle binstubs rspec-core --path exe
% ./exe/rspec -v
RSpec 3.9
  - rspec-core 3.9.2
  - rspec-expectations 3.9.2
  - rspec-mocks 3.9.1
  - rspec-support 3.9.3

この場合は launch.json の "program" のパスが ${workspaceRoot}/exe/rspec になる

余禄

プロジェクトの rspec のバージョンに拘らない場合は、以下のように /usr/local/bin/rsepc を指定しても動作する

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "RSpec - active spec file only",
      "type": "Ruby",
      "request": "launch",
      "program": "/usr/local/bin/rspec",
      "args": [
        "-I",
        "${workspaceRoot}",
        "${file}"
      ]
    }
  ]
}

それと、これは別件ではあるが、 launch.json の設定に関してもう一つ取り上げる
gemspec でエンコーディングのエラーが発生した

Uncaught exception: 
[!] There was an error parsing `Gemfile`: 
[!] There was an error while loading `xxx.gemspec`: invalid byte sequence in US-ASCII. Bundler cannot continue.

見ての通り、エンコーディングの設定が原因である

参考: Encoding issue when using gem 'xcodeproj'

gem 開発時にデバッグする場合の launch.json は、最終的に以下のようになった

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "RSpec - active spec file only",
      "type": "Ruby",
      "request": "launch",
      "program": "${workspaceRoot}/exe/rspec",
      "args": [
        "-I",
        "${workspaceRoot}",
        "${file}"
      ],
      "env": {
        "LANG": "en_US.UTF-8",
        "LC_COLLATE": "en_US.UTF-8",
        "LC_CTYPE": "en_US.UTF-8",
        "LC_MESSAGES": "en_US.UTF-8",
        "LC_MONETARY": "en_US.UTF-8",
        "LC_NUMERIC": "en_US.UTF-8",
        "LC_TIME": "en_US.UTF-8",
        "LC_ALL": "en_US.UTF-8"
      }
    }
  ]
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RSA署名検証って何をどうしてるの?

この記事はRubyのOpenSSLライブラリを使ってRSA署名検証がどのように行われるのか疑問に思いコードを書いてみた備忘録のようなものです。
公開鍵暗号やRSA暗号について詳しく踏み込むような内容ではありません。

公開鍵暗号方式の署名という行為はどうやらメッセージを秘密鍵で暗号化したものであるということみたいです。
(RSA暗号での署名はメッセージを秘密鍵で 復号化 するというのが正しいという話もあります。RSAの産みの親がそう言っていたのだとか。)
ここでは署名検証は署名を公開鍵で 復号化(public_decrypt) して確認するので署名は秘密鍵で 暗号化 という定義にしておきます。

それでは、Rubyを使って署名の作成と署名検証を行っていきます。

require 'openssl'

# 鍵の作成
rsa = OpenSSL::PKey::RSA.generate 2048

# メッセージ
M = "署名検証手順確認"

# ハッシュ関数指定
digest = OpenSSL::Digest::SHA256.new

# 署名作成
sig = rsa.sign(digest, M)
=> "#I\xE1\xE9\xE3\x95\xCE\xE2U\x8EwU\xF49a\f\xE7\x11\xF5\xC5|H\xF0\xB1\x9Ct\xE5\xB3\x93Tj\xD2\xFC,\x0F1+\xB9\x88p\xEEe\x99#\xCC\xB3\x81\x98U\x01Uc\xC3*\x92\v\xD3(\xE9~&@{\xA1S\x83\v\x83\xFF\xDF\xB8\x82r\xC8\x85|\xC1^\x9F>\xDAc\x17\x9A\xEB\xA8\x1E\xA8\xA4v\xC0\xA3\x98f\xFF\x87^\xFB9#\x98l\xE1\xA9\xB5g-\a\xC3\xAD\x17&\x8C\x84\xAD\x06\xB7\x04c\xA9\xB4{w\x15\xDB\f\xCFQ\x91\xF8t\x16\x8A\x8A\xBC\xB2\xC5H\xD1\xC8p}\xC7\xD6\a\x0F'm\xDB\tT\xDF4\xAAv\xF1\xD4\x14\x86\xD0\x82?\xA6\xB8\xC8\x91\xA8Su\x81Yc9\x83\x94$\x96I\xC9%\xE3\x82\xD6\xB6j\xD1\xB9\xDB\xE0\xD80=v\xBD\n\xDC\xFB:\xC9\x01\xA6\xF3\xC2\xBAT\xAE\x98\xE7B\x10\xE7$\x12_\xEC\x1Ar\x86B\xDEC<:nfV\x12z\xC8%Ng\xF0\xCF\xAA\xD2\x94\xC1\xC0\x1C\x9D,>\xF7+\x83s\xCBX)!\x90)W\xF0\xEA"

# 署名検証
rsa.verify(digest, sig, M)

上記で署名の作成と検証ができています。

・・・
・・・

検証って一体何してるの?
って自分は思いました。

こちらの記事と記事中にあるブログの画像を参考にさせていただきました。

署名検証は
- メッセージをハッシュ関数でハッシュ値H1を出す
- 署名を公開鍵で復号化しEMSA-PKCS1-v1_5のデータを得る
- EMSA-PKCS1-v1_5データのパディングを除去してH2を得る
- H1とH2を比較する

という手順を踏めばRubyのOpenSSLでは署名検証出来そうです。(本当は参考記事のようにさらに処理をしないといけないのかなと思います。)

実際にコードで実験してみます。

# 署名を復号化
decrypted = rsa.public_decrypt(sig)
=> "010\r\x06\t`\x86H\x01e\x03\x04\x02\x01\x05\x00\x04 Un\x1A\x02^\xDE\x11Q\x9C~\xD3c\xE7H}6\x88\xAF\x1E\xC5\xAC7#\xB6\xC4@\"_]\xB9W\x10"

# 2048bit鍵なので32byteを取り出す
m_hash = decrypted[-32, 32]
=> "Un\x1A\x02^\xDE\x11Q\x9C~\xD3c\xE7H}6\x88\xAF\x1E\xC5\xAC7#\xB6\xC4@\"_]\xB9W\x10"

# メッセージのハッシュ値
digest.digest(M)
=> "Un\x1A\x02^\xDE\x11Q\x9C~\xD3c\xE7H}6\x88\xAF\x1E\xC5\xAC7#\xB6\xC4@\"_]\xB9W\x10"

m_hash == digest.digest(M)
=> true

署名を復号化したものと、メッセージをハッシュしたものが一致しました。
検証成功ですね。

検証失敗パターンも確認しておきましょう。

m_hash == digest.digest("検証失敗するよ")
=> false

想定通り失敗しました。

署名検証 verify の中ではこのような処理が行われているようです。

僕と同じ疑問を持った方の参考になれば幸いです。

この領域に強い方のご指摘や修正リクエスト大歓迎です。

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

実務経験1週間のまとめ

【何をやったか】

1. psotmanを利用したAPIテスト

→今回のプロジェクトでは、フロント開発とバックエンド開発が分かれており、
 バックエンド側だけで修正し、テストを実施するために必要

2. 軽微な修正(API返答レスポンスの修正など)

→イシューに上げられた、レスポンスがおかしいところや
 HTTPステータスがおかしい点の修正

【実務で使うため、学習すべき内容】

下記は、それぞれ別の記事でまとめていこうと思う。

SELECT文

Docker

Git

細かい便利技

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

《未経験→webエンジニア》実務5日目

昨日投稿できなかったので、今日投稿します。

この記事の目的

自分がやったこと、知らなかったこと、やるべきことを明確にし
1日あたりの成長速度を速める。

【今日やったこと】

APIテスト
イシューに上がっている軽微な内容の修正

【知らなかったこと】

・APIのレスポンスは、コントローラーに種別で作られている
・メモ帳のデフォルト設定で””が逆さになっており、エラーが起きる
・現在いるブランチ名はVScodeの左下に表示されている!

知らなかったテストbreakman→セキュリティ系のテストを行ってくれる
今回の案件では、breakman,rspec,rubocopの3段階でテストする
expected: "期待されている文言" got: "実際の文言"

ここが違わないかをチェックする
API文言の修正だけでなく、ステータスコードもセットで直すようにする
HTTPのステータスコードhttps://qiita.com/terufumi1122/items/997e24dde87f807e3944

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

Rails 6で認証認可入り掲示板APIを構築する #14 seed実行時間の表示

Rails 6で認証認可入り掲示板APIを構築する #13 認証ヘッダの付与

完成図

$ rails db:seed 
-----user start-----
count from: 0
count to: 10
3.4542s
-----user end-----
-----post start-----
count from: 0
count to: 13
0.1095s
-----post end-----
3.5883s

seedsファイルの編集

まずはcontrollerの修正と同じく、postはuserから生成するため、user_seeds.rbから作っていきます。

db/seeds/user_seeds.rb
# frozen_string_literal: true

unless User.exists?
  10.times do |i|
    email = "test#{i + 1}@example.com"
    User.create!(email: email, password: "password",
                 uid: email, provider: "email", name: Faker::Name.name)
  end
end

とりあえず10ほぼユーザーを生成してみましょう。

続いて、post_seeds.rbを上記で生成したユーザーに所属させる形で生成します。

db/seeds/post_seeds.rb
# frozen_string_literal: true

unless Post.exists?
  users = User.all
  users.each do |user|
    Random.rand(0..3).times do
      user.posts.create!(subject: Faker::Lorem.word, body: Faker::Lorem.paragraph)
    end
  end
end

全ユーザーを取得し、0から3件ランダムで投稿を生成します。
Random.rand(x..x)はseedでよく使う手法なので覚えておくと良いです。

db/seeds.rbの編集

db/seeds.rb
# frozen_string_literal: true

seed_models = %i[user post]

all_process_time = Benchmark.realtime do
  seed_models.each do |model|
    puts "-----#{model} start-----"
    puts "count from: #{model.to_s.classify.constantize.count}"
    process_time = Benchmark.realtime do
      require "./db/seeds/#{model}_seeds"
    end
    puts "count to: #{model.to_s.classify.constantize.count}"
    puts "#{format('%.4<time>f', time: process_time)}s"
    puts "-----#{model} end-----"
  end
end
puts "#{format('%.4<time>f', time: all_process_time)}s"
$ rails db:seed 
-----user start-----
count from: 0
count to: 10
3.4542s
-----user end-----
-----post start-----
count from: 0
count to: 13
0.1095s
-----post end-----
3.5883s

modelが増えてきて処理時間が伸びてきたら、どこがボトルネックになっているのか調査しやすくなるはずです。

続き

Rails 6で認証認可入り掲示板APIを構築する #15 pundit導入
連載目次へ

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

[Rails] User検索機能を実装する

概要

Railsプロジェクトで掲示板サイトを作成中、User検索機能を実装する機会がありました。
備忘録としてここに手順を残しておきます。

ルーティングの追加

GET /users/searchでUsersコントローラのsearch アクションにルーティングされるようにconfig/routes.rbを編集します。

config/routes.rb
resources :users do
  get :search, on: :collection
end

usersコントローラーの編集

searchアクションを作成します。
検索文字列はsearch_keywordフィールドへの入力とするので、params[:search_keyword]で取得します。

app/controllers/users_controller.rb
def search
    if params[:search_keyword].present?
      @users = User.where('name LIKE ?', "%#{params[:search_keyword]}%")
    else
      @users = User.none
    end
  end

ビューの追加

app/views/users/search.html.erb を作成します。

views/users/search.html.erb
<h1>User search</h1>
<%= form_tag search_users_path, method: :get do %>
  <%= text_field_tag :search_keyword %>
  <%= submit_tag "Search", username: :nil, class: "button is-info" %>
<% end %>
<%= render 'users/users', users: @users %>

User の情報が入っている app/views/users/_users.html.erb を既に作っていたので render で呼び出しました。

完成

スクリーンショット 2020-09-19 10.44.17.png

検索するとこんな感じです。
スクリーンショット 2020-09-19 10.56.58.png

以上でRailsプロジェクトにUser検索機能を実装することができました。
ありがとうございます。

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

[Rails]ストロングパラメーターにuser_id(外部キー)を記述する方法

投稿内容

今回はストロングパラメータに外部キーを記述する方法をアウトプット。

実装方法

controller.rb
private

def item_params
  params.require(:item).permit(:name, :description, :category, :brand, :condition, :shipping_cost, :prefecture_id, :shipping_day, :price, images_attributes: [:image]).merge(user_id: current_user.id)
end

このようにmergeメソッドを使い記述。
今回の場合は、ログインしているユーザーのidがuser_idとしてパラメーターに保存される。

最後に

今回は簡潔にアウトプット!参考にして下さい!

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

bundle updateをしても、繰り返し促される時の解決法

To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.1.4`

このようなエラーが繰り返し表示されたときは、

gem update --system  

を実行すると解決する場合があります。

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

form_withメソッドの使用方法

【概要】

1.結論

2.どのように使用するか

1.結論

<%= form_with(model or URL, ) do |f| %>と使用する!

2.どのように使用するか

自分はこのように使用しました!

views/time/new.html.erb
<%= form_with(model: @time, local: true) do |f| %>

自分は”model”に@timeでインスタンス変数を入れました。
modelの後にurlでpath指定してあげるとpathによってmethodも自動で振り分け機能になり、そしてアクションも指定できます。またpathの中に(****.id)と指定するとidを引っ張った状態でアクションを行えます!

class=""指定してあげて、cssにあてることもできます!

|f|はヘルパーメソッドに適用でき、form_withの中身を"f."がついているものに入れ込むことができます!
|f|は変数のようなものなので使いやすい名前で統一したもらえばOKです!

f.label
f.checkout
f.password_field
f.email_field等のヘルパーメソッドに適用できます!


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