20210301のRubyに関する記事は23件です。

Rails Slackへメッセージを送信

前提条件

・ruby 2.6.6
・Rails 6.0.3.2
・macOS Catalina バージョン10.15.7

概要

 webアプリ内で特定のアクションが起きた時にSlackへ通知する機能を実装したいと思い
実装の備忘録としてここに記す。
 思ったよりかなり簡単だったので、ぜひご自身のwebアプリに活用していただけると幸いです。

gem 'slack-notifier'の導入

 slackへ通知を送るにはgemを導入する必要があります。

Gemfile
gem 'slack-notifier'
ターミナル
bundle

これでgemの導入が完了します。

Slack側準備

 まずチャンネルのWebhook URLを取得するために、下記のURLにアクセスします。 

 https://slack.com/services/new/incoming-webhook

 ここでRailsからの通知を受け取るチャンネルを設定します。既存のチャンネル、もしくは新規にチャンネルを作ってもどちらでも可能です。
 チャンネルを入力後、Incoming Webhook インテグレーションの追加をクリック。
その後に表示される、Webhook URLを取得します。

Slackに通知を飛ばす

 飛ばしたいアクションの中に

notifier = Slack::Notifier.new(
  'WEBHOOK_URL', 
  channel: '通知を送りたいチャンネル名',
  username: 'notifier',
)
notifier.ping 'ユーザーが投稿を作成しました!'

と書き込みます。
最後に先ほど取得したWebhook URLを'WEBHOOK_URL'に入力します。
これで実装が完了となります。実際にアクションを起こし確認してみてください。
また、そのままWEBHOOK_URLを書き込むより、gemのdotenv-railsを使用したりして
環境変数として設定するといいと思います。

参考文献

・RailsプロジェクトでSlack通知を実装する
https://tech.mof-mof.co.jp/blog/rails-slack-notifier/

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

単体テストコードでエラ〜メッセージすら出なかった話

こんばんは。

本日は初投稿です。
今回の内容は単体テストコードで発生してしまったあるトラブルについてです。

WHAT

まずは下の画像をご覧ください。
単体テストコード.png

カード情報に不備があるとき、「Token」がないとしたテストコードでエラーが発生していますね。
これは何が発生しているのかというとエラーを期待する私に対してコンピュータが「エラーなんて発生していない」と返答をしているのです。

医者と患者を例にすると患者が「私風邪っぽいです」と申告したとします。

Failure/Error: expect(@address_form.errors.full_messages).to include("Token can't be blank")
 「ウチ風邪っぽいねん」

それに対して

expected [] to include "Token can't be blank"
「いや健康でっせ」

医者は診断の結果「健康です」と返してきました。
何が原因なのでしょうか?探っていきましょう。

WHY

まず原因として考えられるのは、
単体テストコードの記述ミス(スペルミス、構文エラー)ですが、もしもこれが正解なら
ターミナルにはsyntaxエラーという別の返答を期待しますよね。
しかし、今回の事例ではそのような記載はなかったのでこの仮説はなしです。

では何が原因か?
それはバリデーションにありました。
今回に限らず言えることとしてテストコードとは自分のかけたバリデーションがうまく機能しているのかを確かめる言わば「答え合わせ」です。
そんな答え合わせで発生した今回の事例modelsを見に行きましょう。

 with_options presence: true do
     validates :user_id
     validates :item_id
     validates :street_address, format: {with:/\A[ぁ-んァ-ン一-龥々]/, message: "Can't be blank"}
     validates :postal_code, format: { with:/\A\d{3}[-]\d{4}\z/ , message: "is invalid. Include hyphen(-)" }
     validates :municipality,  format: { with: /\A[ぁ-んァ-ヶ一-龥々]+\z/, message: "is invalid"}

はい、Tokenにバリデーションをかけていないどころかそれすらありません。
これが原因ですね。

というわけで

validates :token

としたことで事案は解消しました。

結論

今回の件で学んだこととしてテストコードから返されるメッセージが空白[]の場合、
expected [] to include
バリデーションを真っ先に疑った方がいいです。

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

【Rails】 バッチ処理でのエラー「sudo: systemctl: command not found」

はじめに

今回は、とあるスクールのカリキュラムを参考に、初めてバッチ処理を実装していた時の話です。
cronを利用して削除プログラムを定時処理として実行させようとしていたところ、エラーが発生してしまいました。
自身の備忘録として、また同じようなエラーに遭遇された方の参考になればと思い投稿させて頂いております。

(参考)
・crontabコマンド
https://it-mikeiken.com/crontab-command

・CentOSのバージョン確認
https://ja.stackoverflow.com/questions/66504/centos%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%A2%BA%E8%AA%8D

開発環境

ruby 2.6.3
Rails 5.2.4.4
AWS Cloud9(Amazon Linux)

エラーの内容

推奨されていたコマンドがこちらですが、どちらも同じエラーで弾かれてしまいます。

terminal.
# cronが起動しているか確認するコマンド
$ sudo systemctl status crond
sudo: systemctl: command not found

# cronを起動するコマンド
$ sudo systemctl start crond
sudo: systemctl: command not found


解決策

以下のコマンドを実行することで、cronを起動、そして起動しているかを確認することが出来ました。

terminal.
# cronが起動しているか確認するコマンド(どちらも可)
$ sudo service crond status
            or
$ sudo /etc/rc.d/init.d/crond status

# cronを起動するコマンド(どちらも可)
$ sudo service crond start
            or
$ sudo /etc/rc.d/init.d/crond start


エラーが起きた原因

CentOS6以前とCentOS7以降からでは、コマンドが変わっているという記事を発見しました。
そこで、自身のバージョンを確認してみようと思い、以下のようにコマンドを入力すると、「No such file or directory」、つまり「そのようなファイルやディレクトリはありません」と言われてしまいます。

terminal.
# CentOSのバージョンを確認するコマンド
$ cat /etc/redhat-release
No such file or directory

この時点で、そもそもOSってなんだっけ?と思い始めたわけです。
OSについて検索をしてみると、ある記事に「システム全体を管理し、様々なアプリケーションソフトを動かすための最も基本的なソフトウェア」と定義されていました。
更に調べてみると、私が開発環境で使用しているAWSのCloud9、「Amazon Linux」もCentOS/RHEL系に分類されるLinux OSの一つだということが分かりました。OSの一つではあるが、CentOSとは異なるためファイルがないと言われてしまったということです。
Amazon Linuxのバージョンを調べるコマンドも見つけたので、一応下記に掲載しておきます。

terminal.
# Amazon Linuxのバージョン確認コマンド
$ cat /etc/system-release
Amazon Linux AMI release 2018.03


結論

今回自身の結論としましては、OSの種類が違うとコマンドが変わるということで一度納得をすることにしました。まだまだ知識不足で、これらのコマンドがどう違い、どのOSにどのコマンドが正しいかというところまでは理解することが出来ませんでしたので、今後の課題としたいと思います。
もし詳しい方がいらっしゃいましたら、ご教授いただければ嬉しく思います。

終わり

今回は以上になります。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になればと思っています。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。

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

A server is already running. 対処方法

前提条件

・ruby 2.6.6
・Rails 6.0.3.2
・macOS Catalina バージョン10.15.7

概要

ローカル環境でrails sを走らせようとしたが
"A server is already running"
というエラーが発生した。その対処法を記述する。

1.再起動をする

ターミナルを全て閉じ再起動する。
これで解決する場合もあるみたい。私はやったことはない。

2.tmp/pids/server.pid.を削除する

私はこの方法で毎回解決している。
ターミナルで"A server is already running"の後に
Check /Users/・・・・/tmp/pids/server.pid.
と出ている。
サーバーを落とす際にこのファイルが消されるはずなのだが、何らかの不具合で
消されていない状況が発生するようだ。
Finderからアクセスし、直接"server.pid."を削除
これで解決。

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

「action_args」Gemから学ぶ「Ruby」④ ソース分析3日目

本日はabstract_controller.rb分析

「action_args」Gem分析3日目です。
abstract_controller.rbを見てみます〜

学習ポイント1

params_handler.rb
module ActionArgs
  module ParamsHandler
    refine AbstractController::Base do
     ...
    end
  end
end
abstract_controller.rb
require_relative 'params_handler'
using ActionArgs::ParamsHandler

Kernel.#require_relative: 現在のファイルからの相対パスでrequireします。
Module#using: 引数で指定したモジュールで定義された拡張を現在のクラス、モジュールで有効にします。

instance method Module#refineについてはこちら参考

refine
class C
  def foo
    puts "C#foo"
  end
end

module M
  refine C do
    def foo
      puts "C#foo in M"
    end
  end
end

x = C.new
x.foo # => "C#foo"

using M

x = C.new
x.foo # => "C#foo in M"

params_handler.rbでrefineしたActionArgs::ParamsHandlerusingを使って有効にしている。

学習ポイント2

abstract_controller.rb
module ActionArgs
  module AbstractControllerMethods
    # controllerのactionが実行される前に呼ばれる
    def send_action(method_name, *args)
      return super unless args.empty?
      return super if !defined?(params) || params.nil?

      strengthen_params! method_name
      values, kwargs_values = extract_method_arguments_from_params method_name
      if kwargs_values.any?
        super method_name, *values, **kwargs_values
      else
        super method_name, *values
      end
    end
  end

  module AbstractControllerClassMethods
   # controllerにstrong prameterを指定したら呼ばれる(例:permits :name, :age)
    def permits(*attributes, model_name: nil, **kw_attributes)
      @permitted_attributes, @permitting_model_name = attributes << kw_attributes, model_name
    end
  end
end

AbstractController::Baseについてはこちら参照

instance method Object#send

send
class Book
  def title
    puts "ruby"
  end
end

book = Book.new
book.title
=> ruby

# sendを使う場合
book.send(:title)
=> ruby
abstract_controller.rb
AbstractController::Base.send :prepend, ActionArgs::AbstractControllerMethods

send_actionをOverrideする目的でprependしている
AbstractController::Base.prepend(ActionArgs::AbstractControllerMethods)でも同じじゃないかと思いますがsendを使う理由は何だろうと思って調べたらsendの場合はメソッドがPrivateの場合でも実行できることだったのでそれが理由じゃないかと思いました。

class Book
 private

 def title
  "ruby"
 end  
end  

book = Book.new
=> #<Book:0x00007facf3d6ee60>
book.title
NoMethodError: private method `title' called for #<Book:0x00007facf3d6ee60>
from (pry):15:in `__pry__'

book.send(:title)
=> "ruby"

AbstractController::Baseソースのsend_actionはこちらを参考

instance method Module#prepend

prepend使い方
module X
  def foo
    puts "X1"
    super
    puts "X2"
  end
end

class A
  prepend X

  def foo
    puts "A"
  end
end

A.new.foo
=>
X1
A # superはA.fooを意味
X2
abstract_controller.rb
AbstractController::Base.singleton_class.send :include, ActionArgs::AbstractControllerClassMethods

なぜこちらでsingleton_classを使っているのかはまだわからないので今後わかるようになったら追記

instance method Object#singleton_class: レシーバの特異クラスを返します。まだ特異クラスがなければ、新しく作成します。

instance method Module#include: モジュールをインクルードします。

includeするpermitについての詳細はこちら

以下のようにControllerにpermitsを追加すると上記に定義したAbstractControllerClassMethods.permitsが処理する

permits
class UsersController < ApplicationController
    permits :name, :age

    ....
end
include
module M
end
class C1
  include M
end
class C2 < C1
  include M   # この include は無視される
end

p C2.ancestors  # => [C2, C1, M, Object, Kernel]

最後に

普段意識しないで使ってた機能(permits,send_action)がどこで処理されるのかわかるようになりました。

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

Boolean型の記述方法について

orangeを2倍にした数がappleの数以上であった場合にのみ
trueを返しそれ以外はfalseを返したい時、以下のように記述できる。

def fruit(apple, orrange)
  orrange * 2 <= apple
end

このような書き方は冗長になる。

def fruit(apple, orange)
  if orange*2 <= apple
    true
  else
    false
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】初心者がるりまを読んで初めて知ったメソッドをまとめてみる

最近るりまを読んでいるので、学習したメソッドから有用そうなものをまとめていこうと思います(追加予定)
たのしいRubyを読み終わったあたりの初心者が対象の記事です

Arrayクラス

eql?(other) -> bool

selfotherを比較します
普段使うなら==で事足りますが、11.0を区別するといった場合に有用です

assoc(key) -> Array | nil

selfを配列の配列と仮定して[0]keyと等しい最初の配列を返します
元々はハッシュのような配列のためのメソッドですが、特殊な形式の配列に使えそう

[[1, 10], [2, 20]].assoc(2) #=>[2, 20]

[1]を検索する rassoc というメソッドもあります

bsearch { |x| ... } -> object | nil

rubyで二分探索ができます
使い道…?

dig(idx, ...) -> object | nil

ネストしたオブジェクトを参照して返します
[]を連続して使うのと結果は同じですが見栄えが良くなります

ary = [[1, [2, 3]]]

ary[0][1][1]     #=> 3
ary.dig(0, 1, 1) #=> 3

fetch(nth) -> object

idxの要素を返します
[]と違うのは、要素がなかった場合の挙動です
1. 第二引数を与えているとその値を返す
2. ブロックを与えているとnthをブロック変数としてブロックを評価  しその戻り値を返す
3. どちらもなければ例外発生

if文を書かなくも様々な対応ができる素晴らしいメソッド

values_at(*selectors) -> Array

selectorsのインデックスに対応する要素を配列で返します
インデックスの指定方法は[]と同じです

rindex(val) -> Integer | nil

valと等しい最後の要素を返します
index の逆バージョンですね

sample -> object | nil

selfからランダムな要素を一つ返します
引数を与えた場合(たとえ1でも)その個数のランダムな要素を配列で返します

reverse_each {|item| ... } -> self

selfを逆順にブロック変数に代入しブロックを実行します
見た目ではreverse.each {|item| ...}とほぼ変わりませんが、たぶん処理効率が良いです

dup -> Array

selfのコピーの配列を返します
レシーバと戻り値は別のオブジェクトですが、要素は同じオブジェクトを参照しているので、完全なコピーを作るには Marshalモジュール がオヌヌメです

zip(*lists) -> [[object]]

selflistsの各要素からなる配列の配列を生成して返します

ary1 = [1, 2, 3]
ary2 = [4, 5, 6]

ary1.zip(ary2) #=> [[1, 4], [2, 5], [3, 6]]

transpose

rotate(cnt = 1) -> Array

cntの位置の要素を先頭とした配列を返します

ary.rotate    #=> [2, 3, 4, 1]
ary.rotate(2) #=> [3, 4, 1, 2]

破壊的なrotate!もあります

delete_at(pos) -> object | nil

インデックスでdelete出来ます

delete_if {|x| ... } -> self

ブロックの評価が真(nil, false以外)になった要素を削除します
reject! と違い、変化がなくてもselfを返すので中々便利

ブロックの評価が偽になった要素を削除する keep_if もあります

drop(n) -> Array

先頭からn個の要素を削除して残った配列を返します
削除した要素を返す shift と違い、残った要素を返すので使い分けができますね

ブロックで判定を行う drop_while もあります

flatten(lv = nil) -> Array

selfを平坦化した配列を返します

有名なメソッドですが、実は引数を取れるようです
lvを与えるとその深さまで平坦化します

ary = [1, [2], [3, [4]]]

ary.flatten    #=> [1, 2, 3, 4]
ary.flatten(1) #=> [1, 2, 3, [4]]

uniq -> Array

selfから重複した要素を削除した配列を返します
このメソッドはブロックをとることができ、ブロックを与えた場合、各要素をブロック変数としてブロックを評価し、その戻り値が重複した要素を削除します

["Alice", "alice", " Tony"].uniq(&:downcase) #=> ["Alice", " Tony"]

to_h -> Hash

self[key, value]のぺアの配列として解析した結果をHashにして返します

有名なメソッドですが、実はブロックを取れます
ブロックが与えられた場合、各要素をブロック引数としてブロックを評価し、その戻り値を[key, value]のペアの配列として解析します

[1, 2].to_h { |x| [x, x * 10] } #=> {1=>10, 2=>20}

each_with_objectなどでコーディングするよりもかなり文章量が減りますね

transpose -> Array

selfを行列と見立て、縦列と横列を入れ替えます

ary = [
  [1,2],
  [3,4],
  [5,6]
]

ary.transpose #=> [[1, 3, 5], [2, 4, 6]]

最後に

るりまをよむのはいいぞ。(るりまおじさん)
表現力が上がり、メソッドの詳しい挙動が知れることもあり、良い事づくめです
紹介してないメソッドも沢山あるのでぜひ

初心者は ruby style gide を読むのもオススメです

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

【Rspec】Error: raise WrongScopeError の解消【原因:rspec_railsのバージョン】

Rspecを初めて書こうと思い、色んな記事を参考にrspec_rails をインストールしてテストを書きました。いざRspecを走らせてみると、以下の様なエラーが発生しました。

Failure/Error:
       raise WrongScopeError,
             "`#{name}` is not available from within an example (e.g. an " \
             "`it` block) or from constructs that run in the scope of an " \
             "example (e.g. `before`, `let`, etc). It is only available " \
             "on an example group (e.g. a `describe` or `context` block)."

       `name` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). It is only available on an example group (e.g. a `describe` or `context` block).

どうやらnameがスコープの範囲外にあるのがダメと言っています。しかしnameに全く身に覚えがなかったので途方にくれていました。

色々調べてみると、rspec_railsのバージョンが古いと同様なエラーが発生する様です。

そこでrspec_railsのバージョンを現在の最新バージョン(4.0.2)に変更したところ、解決しました。
同様のエラーに悩んでいる人の助けになれば幸いです。

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

作品を評価し合うwebアプリ作ってみた[個人開発]

今回作ったもの

タイトルにもある通り、絵や音楽、プログラムなどの作品を評価し合うコミュニティサイト「PRORP」です。

4598734.png

なぜ作ったか

ヤフー知恵袋でこんな質問を見つけました。

塾に通ってるなら他人からの評価は簡単に受けれますが、独学の場合なかなかしっかりと評価を受けるときってありません。
以外に他人の評価って欲しがってる人が多いのかなと思い作りました。

工夫した点

投稿一覧ページをなくした
公開したばかりでユーザーも投稿も少ないのでユーザーが投稿が見る方法は検索だけにしました。
使いずらいですが最初はしょうがないんです。すみません(__)

★評価機能
25478.png

Raty.jsというものを使い実装しました。まあまあ簡単です。
平均値も求められます。

開発効率化のために

今回、初めて土台アプリを作ってみました。
というのもwebアプリ開発って結構同じことの繰り返しじゃないですか。CRUD、コメント機能、いいね機能、通知機能など
どんなアプリを作るにしろ必要な機能ってありますよね?
それを毎回毎回作ってたら非効率的だなと思いある程度、完成してる土台アプリを先に作りそれをコピーしてPRORPを作りました。

土台アプリでの開発の感想としてはとにかく早いです
当たり前ですが新しく1から作るよりカスタムするほうが早いので、土台アプリは作ってよかったなーと思ってます。

土台アプリに実装した機能としては

  • CRUD
  • コメント機能
  • いいね機能
  • 通知機能
  • ヘッダー、フッター、2カラムレイアウトなどのCSS
  • 新規登録、ログイン機能
  • ユーザーIDとの紐付け

くらいです。もちろん土台アプリなので汎用性重視です。

使ったgem

(省略)

gem 'ridgepole'
gem 'slim-rails'
gem 'html2slim'
gem 'pry-rails'
gem 'devise'
gem 'kaminari'
gem 'activeadmin'
gem 'rack-attack'
gem 'rails-i18n'
gem 'devise-i18n'
gem 'devise-i18n-views'
gem 'carrierwave'
gem 'fog-aws'
gem 'dotenv-rails'
gem 'rmagick'

まとめ

土台アプリは今度からも使っていこうと思います。
作りたいものがない、という方は土台アプリをまず作ってみるといいかもしれません。

まぁとにかくこのPRORP使ってくれ!(誘導下手)

ツイッターもやってます!
https://twitter.com/yamada1531

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

【超かんたん】Fakerを使ってダミーデータを作成しよう!

Fakerを利用してアプリにダミーデータ生成します。
今回は、deviseで作成したusersテーブルにダミーデータを投入していきます。

Fakerとは

Fakerとはランダムな値を生成してくれるGemです。人名、メールアドレス、パスワードの生成はもちろんのことゲームのタイトルやアニメのキャラクター名なども生成してくれる遊び心がいっぱい詰まったGemです。公式ドキュメント

では、さっそく使って行きましょう!

Fakerの導入

gamのインストール

Gemfile
gem 'faker'
ターミナル
bundle install

導入完了。

ダミーデータの生成

今回は、deviseで生成されるemailとpasswordの他にnameカラムを追加しています。

deviseで生成されるemailカラムには一意性制約があるのでFaker::Internetfree_emailの間にuniqueと記述しています。

passwordもデフォルトでは6文字以上でないと登録できないのでmin_length: 6と記述しています。この辺りはご自身が制作するアプリの仕様によって適宜変更していきましょう。

db/seeds.rb
50.times do
  name = Faker::Name.name
  email = Faker::Internet.unique.free_email
  password = Faker::Internet.password(min_length: 6)

  User.create(
    name: name,
    email: email,
    password: password,
    password_confirmation: password
  )
end
ターミナル
rails db:seed

それでは確認してみましょう。
user.jpg

ダミーデータが生成されています。

日本語化してみよう

Fakerは一部、日本語対応しています。

日本語化はとても簡単です。
config/application.rbにconfig.i18n.default_locale = :jaという記述を追加するだけです。

config/application.rb
module アプリ名
 class Application < Rails::Application
   # Initialize configuration defaults for originally generated Rails version.
   config.load_defaults 6.0
   config.i18n.default_locale = :ja #追加
   # 省略
 end
end
ターミナル
rails db:seed

user2.jpg
日本語化完了。

番外編

ここからは番外編です。
ダミーデータの名前を変更してみましょう。
詳しくは公式ドキュメントを参照しましょう。

まずはja.ymlファイルを作成します。

ターミナル
touch config/locales/ja.yml

こちらの記述を作成したja.ymlにコピペし日本語化しましょう。
※日本語化できるのは一部だけです。

ポケモンの場合

db/seeds.rb
50.times do
  name = Faker::Games::Pokemon.name #変更
  email = Faker::Internet.unique.free_email
  password = Faker::Internet.password(min_length: 6)

  User.create(
    name: name,
    email: email,
    password: password,
    password_confirmation: password
  )
end
ターミナル
rails db:seed

p.jpg

スーパーマリオの場合

db/seeds.rb
50.times do
  name = Faker::Games::SuperMario.character #変更
  email = Faker::Internet.unique.free_email
  password = Faker::Internet.password(min_length: 6)

 #以下略
ターミナル
rails db:seed

ま.jpg

ドラゴンボールの場合

db/seeds.rb
50.times do
  name = Faker::JapaneseMedia::DragonBall.character #変更
  email = Faker::Internet.unique.free_email
  password = Faker::Internet.password(min_length: 6)

 #以下略
ターミナル
rails db:seed

d.jpg
こちらは日本語対応していないみたいです。

以上になります。

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

Ruby 出力について

出力について

Rubyには処理の結果を目に見える形で出力するためのメソッドとして様々な出力系メソッドが存在します。

それぞれの違いを調べ、必要な場面で使いこなせるようにまとめておきます。

・printメソッド:改行を入れずに引数に指定した値をそのまま出力する。

print 123
print 'あいう'
【実行結果】
123あいう

・putsメソッド:末尾に改行が入る形で引数に指定した値をそのまま出力する。

puts 123
puts 'あいう'
【実行結果】
123
あいう

・pメソッド:出力する値と共に型情報(文字列や数値型など)分かりやすい形で出力します。数値はそのまま表示、文字列はダブルクォートで挟んだ状態で表示されます。

p 123
p 'あいう'
【実行結果】
123
"あいう"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby silverに無事合格したので、体験記をまとめてみる(2021/02)

無事ruby silverに合格したので
他、数多くの合格体験記がありますが
私もその例に倣って書いてみることにします。

2021年2月の最終週に初受験。88点で合格しました。
12問までミスしても大丈夫だ!というのが、心理的には大分安心材料で
多少答えに自信がない問題があったとしても、ペースを崩さずに進められました。

前提

私のrubyとの関わり具合です

  • ちょうど業務でrubyを使い始めて1ヶ月くらい(railsに苦戦中)
  • 会社で取得を推奨されたので、重い腰をあげてトライしました
  • 以前からrubyの勉強はちまちましていたが、udemy1,2講座と、所謂チェリー本を少し読んだ程度
  • プログラマー歴自体は3年弱
  • あまり意識は高くない方のプログラマーです。勉強は好きじゃないです(白目)

勉強時間

  • 約1ヶ月くらい前から準備し始めました
  • 平日は基本勉強らしい勉強はせず。休日メインで学習を進めました。
  • 合計学習時間は30時間くらい
  • 集中してガッツリ取り組むより、ながら勉強が多かったです。

勉強に使用したもの

① ruby技術者認定試験合格教本
② Rex
③ 模擬問題集
④ ruby実行環境
⑤ Qiita
⑥ google先生

基本はとにかく問題を解いて、間違えたところや、理解が乏しい部分を中心に
本やwebで調べるという作業を繰り返しました。
他のさまざまな試験の勉強と、特別違うところはないですね。

silverは暗記ゲー なんて書かれたりもしていますが(確かにその側面も大きいですが)
暗記に頼って挑むと足元を掬われるような、いやらしい細かい部分を問われる問題もそこそこ出題されるので
きちんと一つ一つの問題を理解できるまで深堀った方が、結果的に近道かな、と感じました。

①ruby技術者認定試験合格教本

教本
rubyの試験を受けるならまず用意すべき本
私はそもそもrubyに関しての知識が不足していたので、まずはさらっと、silverの試験範囲に関して目を通しました。
1回読んだだけじゃ正直何も頭には入ってこないのですが、その後問題とかを解き始めてから再度この本に戻ってくると
ああ、そういうことだったんだ〜ってなったりして、いい使い方ができたと思います。

問題の解説を読んでもいまいち理解できなかった時に、リファレンス代わりにこの本で調べたりもしました。
(巻末の索引がナイスです)

amazonのレビューにも書かれてますが、silverとgoldの試験範囲が判りづらいのが難点。

用意されている演習問題の80問は、試験の1週間前に解きました。
本番を終えた今振り返ると、どの問題も合格に直結する重要問題ばかりでしたので
この演習問題でちらほら間違えている様だと、受験には時期尚早かもしれません。

②Rex

REx - Ruby Examination
ruby silver, goldの演習問題を、本番さながらに解く事ができるサービス(要gitHubアカウント)
一回通して教本を読んだ後は、Rexを繰り返し解いていました。
最初、50問通して解いてみたところ62点で
(お、最初からこれくらい点取れてたら結構余裕じゃん?)と思ってしまい
そこから勉強に身が入らない期間が続きました(悪いクセ)
その後、複数回解いてみても点が伸びず、焦り始めることになります。
最終的に6回、通して解きました。94点が自己最高得点。

Rexの問題は教本の問題と比べると、コードの細かい違いによる結果の相違が問われる問題が多いと思います。
一見似た様なコードなのに、スペースが一つ空いているだけで得られる結果が違ったりして
何度もそれにひっかかりました。解いている時はムカついていましたが
おかげで、注意深く問題を読むクセがつけられたと思います。オススメです。

③模擬問題集

silver_j.md
gistで公開されているsilverの問題集です。
Rexと同じ様な使い方をして、何度か繰り返し理解できるまで解きました。
本番でも、似た様な問題が実際に出題されました。

35問目と36問目が、全く同じ問題になってるのは気にしない

④ruby実行環境

①~③の問題を解いてみて、よく理解ができなかった問題や
この選択肢はなんで間違いなんだろう?と思った問題は
実際に実行環境で流してみて、出力を確認する様にしていました。
これをするだけで理解スピードが段違いだったと思います。アウトプット大事。
途中途中でputsしたりして、変数の中身がどう変わっていくのかを追ってみたり
少しコードをいじってみて、出力内容がどう変わるのか試してみたりすると
応用力が付いた気になれます。オススメ。

⑤Qiita

問題を解いてみても理解できなかった箇所や
さらにそこから派生した知識を得たい時に、Qiitaで調べると
先人たちが残してくれた記事がいくつもひっかかるのでありがたかったです。

また、④と同様、アウトプットの場としても優秀で
自分がよく間違えた問題や、本番でついミスってしまいそうな箇所をまとめて記事を1つ作りました
ruby silver受験前に個人的な注意ポイントまとめ

公開する際に、間違った事を適当に書くわけにいかないので
自ずと関連する知識まで、ちゃんと調べる必要が出てきます。
この作業によって、苦手だな〜覚えられないな〜と感じていた部分を
なくす事ができ、自信にも繋がりました。
今振り返ってみると、上記でまとめた内容に関連する問題が
本番で3,4問は出題されてました。やっておいてよかった…
後からruby silver受ける方達のためにもなるので、積極的に発信側に回りましょう!

⑥Google先生

教本の解説は分かりやすいですが、淡々と説明が続いていくため
個人的には頭に入りにくく感じました。
より分かりやすく、噛み砕いた説明が欲しい時は、google先生にとりあえず聞いてみると
いい感じにまとめられた記事が見つかる事が多いです。
公式のリファレンスも、サンプルコードがあったりして意外と親切だし、何より絶対間違いがないのでオススメですが
いきなり公式のリファレンスを読みにいっても、分量に圧倒され、モチベが続かないと思うので
他で勉強したところの補強程度に留めておくのが賢い使い方かと感じました。
また、公式はver.3.0準拠になっている点も注意です
(ruby silver試験は2021年2月現在ver.2.1準拠です)

受験当日の流れ

試験開始が14時半からだったので、早めに現着して近くのカフェで最後の見直しを行いました。

具体的には以下の通りです


①教本の演習問題80問を最後にもう一度全て解き直し

この時にはわからない問題がない状態まで理解を進めていたので、自信を持たせる目的が大きかったです。

②Qiita記事の最終確認


自分自身でミスってしまいそうなポイントをまとめておいた事がここで活きました。
また、直前に見直す用に先人の方がまとめていただいた神サイトを見直しました。
組み込み定数や、特殊変数、上書きできない演算子など、単純な暗記項目は直前に見直しておくといいと思います。
Ruby Silver試験前に見直すと幸せになれるメモ

テストセンターでの受験は初めてだったので、どんな感じなのかな〜と思っていましたが
PC毎にデスクが区切られていて、周囲の雑音をシャットアウトするヘッドホンも備え付けられており
集中して受験できる環境が整っていました。
紙とペンも与えられるので、計算が絡む問題を整理しながら解きたい時などに利用できます。
(持参は不可)

同じ時間に受験の受付している方で、一人証明書の不備があって受験できなかった方がいらっしゃったので注意です。
なんなら試験勉強よりもよほど大事です。試験要項はきちんと確認しておきましょう。

本人確認書類は2種類必要です!

A: 写真付きの確認書類(運転免許証・社員証・マイナンバーカードなど)
B: 署名付きの確認書類(クレジットカード・図書館の利用証など)

Bの証書を私もすっかり忘れていて、クレカを受付であたふたしながら提出することとなりました。

気になる方は以下も確認を
試験当日の受験の流れ

90分の試験ですが、正直そんなに時間は使わないと思います。
見直しも兼ねてもう一度頭から全て解き直して、45分くらいで終わりました。
見直しの際に間違いに気づいた問題が1,2問あったので、必ずやりましょう。

問題の所感としては、今まで解いてきた練習問題と、難易度的には変わりないです。

練習問題とほとんど同じ内容の問題:4割
練習問題に関連した事柄について問われた問題:4割
練習問題ではあまり見なかった箇所を問われた問題:2割 くらいだったかと思います。

割合から見ても、練習問題を完璧にできる様にしておく・関連する知識を深堀して覚えておく
これを徹底できれば、合格はさほど難しくない試験だと感じました。12問まで間違えられますしね。

まとめ

  • 各練習問題を何度も解いて、頻出問題の理解を深める!
  • つまづいた箇所を中心に納得するまで深堀して、アウトプットを行う!
  • 暗記項目は試験直前にもう一度確認!
  • 本人確認書類を忘れない事(これ大事) 落ち着いてチャレンジしましょう

新たなrubyist達の、試験合格をお祈りしております。
(次はgoldだー)

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

basic認証を導入する

basic認証をアプリケーションへ導入する

controllers/application_controller.rb にて以下の記述を行う。

class ApplicationController < ActionController::Base
  before_action :basic_auth


  private

  def basic_auth
    authenticate_or_request_with_http_basic do |username, password|
      username == ENV["BASIC_AUTH_USER"] && password == ENV["BASIC_AUTH_PASSWORD"]  # 環境変数を読み込む記述に変更
    end
  end
end

環境変数を使って、ユーザー名とパスワードの設定を行う。
heroku上で管理を行うため以下の記述でセットする。

% heroku config:set BASIC_AUTH_USER='aaaa'
% heroku config:set BASIC_AUTH_PASSWORD='1111'

正しく設定できているかはheroku configでしっかり確認します。
変更したコードをコミットし、Herokuへデプロイします。

% git add .
% git commit -m "Basic認証を導入"
% git push heroku master

これで本番環境へ反映します。

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

【Ruby on Rails】deviseの導入

Ruby on Railsの便利機能であるdeviseの導入手順をまとめます。

環境

Version
MacOS Catalina 10.15.7
Ruby 2.6.5
Rails 6.0.3.5

Gemfile

Gemfileの一番下に下記を記述します。

Gemfile
gem 'devise'

ターミナルで下記コマンドを実行しgemを読み込みさせます。

ターミナル
bundle install

ターミナルで下記コマンドを実行しdeviseを導入します。

ターミナル
rails g devise:install

下記のようなメッセージが出てそれぞれファイルが生成されます。

create  config/initializers/devise.rb
create  config/locales/devise.en.yml

モデル作成

Userモデルを作成します。
ターミナルで下記コマンドを実行します。

ターミナル
rails g devise user

下記のような実行結果がでます。
メッセージにあるようにapp/modelsuser.rbというモデルファイルが生成されます。

ターミナル
Running via Spring preloader in process 1632
      invoke  active_record
      create    db/migrate/2021×××_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

マイグレーションファイル編集

db/migrateに新しく2021×××_devise_create_users.rbというマイグレーションファイルが生成されているので編集が必要であれば編集します。

例えば、元々はユーザー登録にemailpasswordしか必要ないようになっているので、下記のようにstring型nameを追加したりできます。

db/migrate/2021×××_devise_create_user.rb
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

     # 下記を追加
      t.string :name,               null: false

編集したら下記コマンドでマイグレーションを実行します。

ターミナル
rails db:migrate

ビュー作成

devise用のビューを作成します。
下記コマンドを実行します。

ターミナル
rails g devise:views

下記のような実行結果が出てdeviseに関する様々なファイルが生成されたことが分かります。

ターミナル
Running via Spring preloader in process 1841
      invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb

簡易的なビューがこれだけで簡単に生成できてしまいます。

おまけ

バリデーションはモデルに記述します。
下記が例です。

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  # 下記を追加
  validates :name, presence: true
end

カラムにデフォルトのemailpassword以外を追加した場合は、それも保存できるように許可の記述をしないと弾かれてしまいます。
app/controllers/application_controller.rbに以下のように記述します。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  private
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
  end
end

before_action :configure_permitted_parameters, if: :devise_controller?でdeviseに関わる動作については全て起動するように設定します。
そしてその処理としてconfigure_permitted_parametersで今回作成したnameも許可するように記述します。
configure_permitted_parametersという名前は慣習的なものらしいので特に意味はありません。

以上です。

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

[ruby] Google Firebase Storageの簡単な使い方

Spark プラン 無料枠 Storage

https://firebase.google.com/pricing
Firebase_Pricing.png

プロジェクトID

clouds_–_Project_settings_–_Firebase_console.png

バケットID

複数のバケットは持てないので、ここに表示されるドメイン名がそれ(gs://以下)

clouds_–_Storage_–_Firebase_console.png

コードサンプル等

オブジェクトの命名ガイドライン_ _ _Cloud_Storage_ _ _Google_Cloud.png

storage
storage = Google::Cloud::Storage.new(project_id: ENV["FIREBASE_PROJECT_ID"])
bucket
bucket = storage.bucket ENV["FIREBASE_STORAGE_BUCKET_ID"]
ファイルの追加
bucket.create_file "/path/to/file" # バケットのルートに置く
bucket.create_file "/path/to/file", "folder/name/file" # バケットのサブフォルダに置く

# metadataを付けて置く(後で説明するpublic_urlで必要になる)
# tokenはぶつからない、推測されにくいものであれば何でも良さそう
require "active_support/all"
bucket.create_file "/path/to/file", metadata: { firebaseStorageDownloadTokens: SecureRandom.uuid }
ファイルの参照
files = bucket.files # 全部

# Signature: find_files(prefix:?, delimiter:?, token:?, max:?, versions:?)
# 例 a/b/c_d_e.jpg というファイルがあった場合、
# bucket.find_files prefix: "a/b/c_" で見つけられる path.start_with? 的な
files = bucket.find_files prefix: "folder/sub/prefix"

file = bucket.file("path/to/file") # 直接ファイル1つ
更新
bucket.create_file "/path/to/file1", "folder/sub/file" 
bucket.create_file "/path/to/file2", "folder/sub/file"  # 同じところに同じ名前で置く
削除
file.delete
誰でもアクセスできるURL
file.signed_url # => デフォルトで5分間の公開が可能
file.signed_url expires: 1.hour.to_i # => 1時間の公開が可能、最大で7日間

Signed URL example

X-Goog-Expires: The length of time the signed URL remained valid, measured in seconds from the value in X-Goog-Date. In this example the Signed URL expires in 15 minutes. The longest expiration value is 604800 seconds (7 days).

誰でもアクセスできるURL(永続的)
def public_url(file)
  # more like permanent
  URI(
    "https://firebasestorage.googleapis.com/v0/b/#{CGI.escape file.bucket}/o/#{CGI.escape file.name}",
  ).tap do |uri|

    uri.query = {
      token: file.metadata["firebaseStorageDownloadTokens"],
      alt: :media,
    }.to_query
  end
end

firebaseStorageDownloadTokensがここで必要になるので、ファイルを置く時にmetadataの指定が必要になる。
(javascriptからだとアップロードしたファイルのdownloadUrlがメソッドとして提供されているが、rubyだとまだない?)

Qiita
How to Access and Download Files in Cloud Storage

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

【Rails】link_toのxxx_pathが長い時はpolymorphic_pathを使うと綺麗に書けるお話

Railsにはよく使うものとしてlink_tobutton_toがあります。これらはリンクやボタンを生成するヘルパーです。

更に、遷移先のURLやpathを指定することが出来て、以下のように書けます。

<%= link_to '詳細へ', user_path(user.id) %>

では、URLのネストをした場合、xxx_pathはどうなるでしょうか?

例えば、ユーザーは記事を投稿でき、その記事たちをお気に入りに追加できる機能があったとします。

ルーティングはこちら

routes.rb
Rails.application.routes.draw do
  # ユーザーは記事を投稿できる
  resources :users do
    resources :articles do
      # 記事をお気に入りすることができる
      resources :favorites
    end
  end
end

rails routesを叩いてxxx_pathの部分を見てみましょう。

お気に入りの新規保存に使えるであろうfavorites#createはuser_article_favoritesになってます。
これはまだマシですが、もっとネストされたら長くなり、xxx_pathの部分が分かり辛くなるでしょう。

$ rails routes

user_article_favorites     GET    /users/:user_id/articles/:article_id/favorites(.:format)          favorites#index
                           POST   /users/:user_id/articles/:article_id/favorites(.:format)          favorites#create
new_user_article_favorite  GET    /users/:user_id/articles/:article_id/favorites/new(.:format)      favorites#new
edit_user_article_favorite GET    /users/:user_id/articles/:article_id/favorites/:id/edit(.:format) favorites#edit
user_article_favorite      GET    /users/:user_id/articles/:article_id/favorites/:id(.:format)      favorites#show
                           PATCH  /users/:user_id/articles/:article_id/favorites/:id(.:format)      favorites#update
                           PUT    /users/:user_id/articles/:article_id/favorites/:id(.:format)       favorites#update
                           DELETE /users/:user_id/articles/:article_id/favorites/:id(.:format)       favorites#destroy

本題(polymophic_path)を使おう!

Railsにはモデルから生成されたインスタンスに対して、ルーティングをスマートに書ける方法が提供されています。

公式ドキュメントはこちら

結論から言うと、こう書いていたものが、

# user    = User.find(1)
# article = Article.find(1)

<%= button_to 'お気に入りする', user_article_favorites_path(user.id, article.id), method: :post %>

こう書けます

<%= link_to 'お気に入りする', polymorphic_path([user, article, :favorites]), method: :post %>

何をしているかと言うと、polymorphic_pathの引数においたモデルインスタンスを元にリンクを生成してくれています。
今回の場合 users/1/articles/1/favoritesが生成されています。

ポイントは以下の3つです。

  • 基本的に引数にはModelのインスタンスを置く
  • 引数を複数置きたい場合は配列で囲む
  • インスタンスの生成が行われていない場合は、:favoritesのようにシンボル型で引数に置く

今回の例よりさらに複雑になったpathであれば可読性はさらに向上すると思います。
(逆にusers_pathといった短いものにpolymorphic_pathを使うと変に見えるかもしれません。)

polymorphic_pathの応用

先ほどとは別にnewアクションやeditアクションへのリンクを生成したい場合は以下のように書くこともできます。

# user    = User.find(1)
# article = Article.find(1)

<%= link_to '編集へ', edit_polymorphic_path([user, article ]) %>

<%= link_to '新規作成へ', new_polymorphic_path([user, article ]) %>

edit_polymorphic_path([user, article ]) の場合、users/1/articles/1/editのようなリンクが生成されます。

まとめ

RailsにはDRYの法則に則ったヘルパーやメソッドがたくさん用意されています。
今回扱ったpolymorphic_pathは長くなりがちなpathの記述を簡潔に書くことができます。
ぜひお試しください!

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

【Rails】acts_as_listで並べ替え機能を実装

導入

Gemfile
gem 'acts_as_list'
$ bundle install
app/models/task.rb
class Task < ApplicationRecord
  acts_as_list
end
rails g migration AddPositionToTask position:integer
class AddPositionToTask < ActiveRecord::Migration[6.0]
  def change
    add_column :tasks, :position, :integer #カラム名はpositionじゃなきゃいけない
  end
end
$ rails db:migrate
config/routes.rb
resources :tasks do
  member do
    get :move_higher
    get :move_lower
  end
end

同期処理で動かす

/app/contollers/user_controller.rb
def index
  @tasks = Task.all.order(:position) # positionカラムに従いソート
end

def move_higher
  Task.find(params[:id]).move_higher #move_higherメソッドでpositionを上に
  redirect_to action: :index
end

def move_lower
  Task.find(params[:id]).move_lower #move_lowerメソッドでpositionを下に
  redirect_to action: :index
end
app/views/tasks/index.html.erb
<h2>タスク一覧</h2>
<table>
  <% @tasks.each do |task| %>
    <tr>
      <td><%= task.name %></td>
      <td><%= link_to '詳細', user %></td>
      <td><%= link_to "↑", move_higher_task_path(task) %></td>
      <td><%= link_to "↓", move_lower_task_path(task) %></td>
    </tr>
  <% end %>
</table>

非同期処理で動かす

remote: truejs.erbを使って実装。詳細は割愛。

参考: 【Ruby on Rails】部分テンプレート(js.erb)を用いた非同期通信について(基礎/開発) - Qiita

注意点

positionカラムのデータがnullだと動かない。
既存のテーブルに後からpositionカラムを追加した際には、なんらかの方法で全てのデータのpositionカラムに数値を入力する必要がある。

参考

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

【Rails】acts_as_listで並べ替え機能を同期/非同期それぞれで実装

導入

Gemfile
gem 'acts_as_list'
$ bundle install
app/models/task.rb
class Task < ApplicationRecord
  acts_as_list
end
rails g migration AddPositionToTask position:integer
class AddPositionToTask < ActiveRecord::Migration[6.0]
  def change
    add_column :tasks, :position, :integer #カラム名はpositionじゃなきゃいけない
  end
end
$ rails db:migrate
config/routes.rb
resources :tasks do
  member do
    get :move_higher
    get :move_lower
  end
end

同期処理で動かす

app/contollers/user_controller.rb
def index
  @tasks = Task.all.order(:position) # positionカラムに従いソート
end

def move_higher
  Task.find(params[:id]).move_higher #move_higherメソッドでpositionを上に
  redirect_to action: :index
end

def move_lower
  Task.find(params[:id]).move_lower #move_lowerメソッドでpositionを下に
  redirect_to action: :index
end
app/views/tasks/index.html.erb
<h2>タスク一覧</h2>
<table>
  <% @tasks.each do |task| %>
    <tr>
      <td><%= task.name %></td>
      <td><%= link_to '詳細', user %></td>
      <td><%= link_to "↑", move_higher_task_path(task) %></td>
      <td><%= link_to "↓", move_lower_task_path(task) %></td>
    </tr>
  <% end %>
</table>

非同期処理で動かす

app/contollers/user_controller.rb
def index
  @tasks = Task.all.order(:position)
end

def move_higher
  @task = Task.find(params[:id])
  @task.move_higher
  @tasks = Task.all.order(:position)
end

def move_lower
  @task = Task.find(params[:id])
  @task.move_lower
  @tasks = Task.all.order(:position)
end
app/views/tasks/index.html.erb
<div id='task-list'>
  <table>
    <% @tasks.each do |task| %>
      <tr class='body-row'>
        <td><%= task.name %></td>
        <td><%= link_to '詳細', task %></td>
        <td><%= link_to '↑', tasks_path(task), remote: true %></td>
        <td><%= link_to '↓', tasks_path(task), remote: true %></td>
      </tr>
    <% end %>
  </table>
</div>
app/views/_tasks.html.erb
<table>
  <% tasks.each do |task| %>
    <tr class='body-row'>
      <td><%= task.name %></td>
      <td><%= link_to '詳細', task %></td>
      <td><%= link_to '↑', tasks_path(task), remote: true %></td>
      <td><%= link_to '↓', tasks_path(task), remote: true %></td>
    </tr>
  <% end %>
</table>
app/views/move_higher.html.erb
$('#task-list').html("<%= j(render 'admin/tasks/tasks', tasks: @tasks) %>");
app/views/move_lower.html.erb
$('#task-list').html("<%= j(render 'admin/tasks/tasks', tasks: @tasks) %>");

scope

taskテーブルのkindカラムの中で並び替えをしたい場合(kindで絞り込みをした状態でもうまく動くようにしたい場合)、以下のように書く。

app/models/task.rb
class Task < ApplicationRecord
  acts_as_list scope: [:kind]
end

注意点

positionカラムのデータがnullだと動かない。
既存のテーブルに後からpositionカラムを追加した際には、なんらかの方法で全てのデータのpositionカラムに数値を入力する必要がある。

参考

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

【Rails】acts_as_listを使った並べ替え機能を同期/非同期それぞれで実装

導入

Gemfile
gem 'acts_as_list'
$ bundle install
app/models/task.rb
class Task < ApplicationRecord
  acts_as_list
end
rails g migration AddPositionToTask position:integer
class AddPositionToTask < ActiveRecord::Migration[6.0]
  def change
    add_column :tasks, :position, :integer #カラム名はpositionじゃなきゃいけない
  end
end
$ rails db:migrate
config/routes.rb
resources :tasks do
  member do
    get :move_higher
    get :move_lower
  end
end

同期処理で動かす

app/contollers/user_controller.rb
def index
  @tasks = Task.all.order(:position) # positionカラムに従いソート
end

def move_higher
  Task.find(params[:id]).move_higher #move_higherメソッドでpositionを上に
  redirect_to action: :index
end

def move_lower
  Task.find(params[:id]).move_lower #move_lowerメソッドでpositionを下に
  redirect_to action: :index
end
app/views/tasks/index.html.erb
<h2>タスク一覧</h2>
<table>
  <% @tasks.each do |task| %>
    <tr>
      <td><%= task.name %></td>
      <td><%= link_to '詳細', user %></td>
      <td><%= link_to "↑", move_higher_task_path(task) %></td>
      <td><%= link_to "↓", move_lower_task_path(task) %></td>
    </tr>
  <% end %>
</table>

非同期処理で動かす

app/contollers/user_controller.rb
def index
  @tasks = Task.all.order(:position)
end

def move_higher
  @task = Task.find(params[:id])
  @task.move_higher
  @tasks = Task.all.order(:position)
end

def move_lower
  @task = Task.find(params[:id])
  @task.move_lower
  @tasks = Task.all.order(:position)
end
app/views/tasks/index.html.erb
<div id='task-list'>
  <table>
    <% @tasks.each do |task| %>
      <tr class='body-row'>
        <td><%= task.name %></td>
        <td><%= link_to '詳細', task %></td>
        <td><%= link_to '↑', tasks_path(task), remote: true %></td>
        <td><%= link_to '↓', tasks_path(task), remote: true %></td>
      </tr>
    <% end %>
  </table>
</div>
app/views/_tasks.html.erb
<table>
  <% tasks.each do |task| %>
    <tr class='body-row'>
      <td><%= task.name %></td>
      <td><%= link_to '詳細', task %></td>
      <td><%= link_to '↑', tasks_path(task), remote: true %></td>
      <td><%= link_to '↓', tasks_path(task), remote: true %></td>
    </tr>
  <% end %>
</table>
app/views/move_higher.html.erb
$('#task-list').html("<%= j(render 'admin/tasks/tasks', tasks: @tasks) %>");
app/views/move_lower.html.erb
$('#task-list').html("<%= j(render 'admin/tasks/tasks', tasks: @tasks) %>");

scope

taskテーブルのkindカラムの中で並び替えをしたい場合(kindで絞り込みをした状態でもうまく動くようにしたい場合)、以下のように書く。

app/models/task.rb
class Task < ApplicationRecord
  acts_as_list scope: [:kind]
end

注意点

positionカラムのデータがnullだと動かない。
既存のテーブルに後からpositionカラムを追加した際には、なんらかの方法で全てのデータのpositionカラムに数値を入力する必要がある。

参考

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

Custom Validator

最初に

みなさん、Custom Validator使ってますか?

rails guideここにも記載されていますがActiveModel::ValidatorActiveModel::EachValidatorを使って自作のvalidatorを作成することができます。

異なるmodelで同じようなvalidationを実行する場合は以下のようなメリットがあるので是非使っていきましょう。
・modelのコードを減らせる
・specの行数を減らせる
(Validator Classへspecを書けば良いので同じようなspecを減らせます)

実装例

よくあるパターンだと思うのですが異なるmodelでメールアドレスカラムを持っており、同じようなvalidationを実装する場合の実装例を記載します。

  • 以下のようなEmailのValidator Classを作成します。
app/validators/email_validator.rb
# frozen_string_literal: true

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i)
      record.errors.add(attribute, options[:message] || "の形式が不正です")
    end
  end
end
  • deviseを使っていてdeviseと同じEmailのvalidationを使いたい場合は以下のような書き方もできます。
unless value.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i)

unless value.match(Devise.email_regexp)
  • modelでの使い方は以下になります。
class Person < ApplicationRecord
  validates :email, presence: true, email: true
end
  • エラーメッセージは以下のような感じになると思います。
メールアドレスの形式が不正です
  • エラーメッセージを変えたい場合もあると思います。
  • その場合は以下のようにメッセージを個別で設定することができます。
class User < ApplicationRecord
    validates :email, allow_nil: true, email: { message: "正しいメールアドレスの形式で入力してください" }
end
  • 次にspecの書き方のサンプルを記載します。
# frozen_string_literal: true

require "rails_helper"

describe EmailValidator do
  let(:model_class) do
    Struct.new(:mail_address) do
      include ActiveModel::Validations

      def self.name
        "DummyModel"
      end

      validates :mail_address, allow_nil: true, email: true
    end
  end

  describe "#validate" do
    subject { model_class.new(email) }

    describe "登録可能な形式" do
      context "nil は登録できる" do
        let(:email) { nil }
        it { is_expected.to be_valid }
      end

      context "「abc@example.com」は登録できる" do
        let(:email) { "abc@example.com" }
        it { is_expected.to be_valid }
      end
    end

    describe "登録不可能な形式" do
      context "「abc」は登録できない" do
        let(:email) { "abc" }
        it { is_expected.not_to be_valid }
      end

      context "「abcd.@example.com」は登録できない" do
        let(:email) { "abcd.@example.com" }
        it { is_expected.not_to be_valid }
      end
    end
  end
end

最後に

email以外にも画像やURLのvalidationなど使えるところは色々あるのでお試し下さい!

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

RailsでCustom Validatorの実装例

最初に

みなさん、Custom Validator使ってますか?

rails guideここにも記載されていますがActiveModel::ValidatorActiveModel::EachValidatorを使って自作のvalidatorを作成することができます。

異なるmodelで同じようなvalidationを実行する場合は以下のようなメリットがあるので是非使っていきましょう。
・modelのコードを減らせる
・specの行数を減らせる
(Validator Classへspecを書けば良いので同じようなspecを減らせます)

実装例

よくあるパターンだと思うのですが異なるmodelでメールアドレスカラムを持っており、同じようなvalidationを実装する場合の実装例を記載します。

  • 以下のようなEmailのValidator Classを作成します。
app/validators/email_validator.rb
# frozen_string_literal: true

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i)
      record.errors.add(attribute, options[:message] || "の形式が不正です")
    end
  end
end
  • deviseを使っていてdeviseと同じEmailのvalidationを使いたい場合は以下のような書き方もできます。
unless value.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i)

unless value.match(Devise.email_regexp)
  • modelでの使い方は以下になります。
class Person < ApplicationRecord
  validates :email, presence: true, email: true
end
  • エラーメッセージは以下のような感じになると思います。
メールアドレスの形式が不正です
  • エラーメッセージを変えたい場合もあると思います。
  • その場合は以下のようにメッセージを個別で設定することができます。
class User < ApplicationRecord
    validates :email, allow_nil: true, email: { message: "正しいメールアドレスの形式で入力してください" }
end
  • 次にspecの書き方のサンプルを記載します。
# frozen_string_literal: true

require "rails_helper"

describe EmailValidator do
  let(:model_class) do
    Struct.new(:mail_address) do
      include ActiveModel::Validations

      def self.name
        "DummyModel"
      end

      validates :mail_address, allow_nil: true, email: true
    end
  end

  describe "#validate" do
    subject { model_class.new(email) }

    describe "登録可能な形式" do
      context "nil は登録できる" do
        let(:email) { nil }
        it { is_expected.to be_valid }
      end

      context "「abc@example.com」は登録できる" do
        let(:email) { "abc@example.com" }
        it { is_expected.to be_valid }
      end
    end

    describe "登録不可能な形式" do
      context "「abc」は登録できない" do
        let(:email) { "abc" }
        it { is_expected.not_to be_valid }
      end

      context "「abcd.@example.com」は登録できない" do
        let(:email) { "abcd.@example.com" }
        it { is_expected.not_to be_valid }
      end
    end
  end
end

最後に

email以外にも画像やURLのvalidationなど使えるところは色々あるのでお試し下さい!

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

Railsでお気に入り機能を実装する

自己紹介

9月から独学でプログラミング学習を開始し、
11月からスクールを使って学習をしています。
現在はポートフォリオの作成し転職活動中です。
知識を定着させるために、学びをアウトプットしています。
また、これから学び始める方の参考になることを願っています。


開発環境

  • Ruby 3.0
  • Ruby on Rails 6.0.3.4

お気に入り機能を実装する

今回はsocializationというgemを使用します。

▼詳しくは公式のGithubはご覧ください。
https://github.com/cmer/socialization

現在、Userテーブルと映画情報を保存するMovieテーブルがあるとします。
まずは、Gemfileに下記を追加します。

gem 'socialization'

追加したら、bundle installを実行し、gemを追加してください。

次に、rails generate socialization -sを実行します。
実行すると、like.rb follow.rb mention.rbのように
モデルが3つとマイグレーションファイルが3つ作成されます。

- class CreateFollows < ActiveRecord::Migration
+ class CreateFollows < ActiveRecord::Migration[6.0]

上記のようにそれぞれのマイグレーションファイルにバージョンを追加します。
そしてrails db:migrateを実行してください。

1.モデルを編集する

それぞれのモデルに下記を追加します。

class Movie < ApplicationRecord

  act_as_likeable

end
class User < ApplicationRecord

  act_as_liker

end

他にお気に入り機能を追加したいテーブルがある場合は、
同様に、act_as_likeableを追加して下さい。


2.コントローラーを編集する

/controllers/movies_controller.rbを編集します。

class MoviesController < ApplicationController

def favorite
  @movie = Movie.find(params[:id])
  current_user.toggle_like!(@movie)
end

上記のようにfavoriteを追加しています。

current_userを使用するにはdeviseというgemが必要になります。
今回、deviseについては、省略致します。

socializationを導入したことでuser.toggle_like!(movie)というメソッドが使えるようになりました。
これは、

  • お気に入りされていなければ、お気に入り登録をする
  • お気に入りされていれば、お気に入りを解除する

このようなメソッドです。
その他のメソッドについては、公式Githubを参照下さい。

3.ルーティングを編集する

favoriteを追加したので/config/routes.rbを編集しましょう。

Rails.application.routes.draw do

  resources :movies do
    member do
      post 'favorite'
    end
  end

end

メンバールーティングについてはRailsガイドを参照下さい。
https://railsguides.jp/routing.html

4.ビューを編集する

<%= link_to favorite_movie_path do %>
  <% if current_user.likes?(movie) %>
    お気に入り解除
  <% else %>
    お気に入り登録
  <% end %>
<% end %>

先程、指定したルーティングをlink_toで使用しています。
current_user.likes?(movie)
これは、ユーザーがお気に入り登録しているとtrueを返します。


シンプルですが、基本的な機能の実装は以上です。
しかし、Ajax(非同期処理)でお気に入り登録をしたり、
お気に入りの総数を表示したいと思うかもしれません。
この辺りの実装は補足として別記事で行おうと考えています。
何か、至らない点があれば、ご指摘下さい。

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

DockerでRails開発時に,画像の保存先をAWS S3にする

先日,Rails, Dockerでの開発時に画像の保存先をローカルからS3に変更した際の手順を記録した.

開発環境

WSL2 (ubuntu 18.04 LTS)
Docker
- Ruby (2.7.1)
- Rails (6.0.3)

画像をS3に保存する手順

Active Storageがインストールされており,S3バケット作成まで完了していることを想定

Active Storageのインストールは以下のコマンドでできる

terminal
docker-compose run コンテナ名 rails active_storage:install
docker-compose run コンテナ名 rails db:migrate

用意するもの

  • S3バケット情報

    • バケット名
    • リージョン
  • IAMユーザー情報

    • アクセスキー
    • シークレットアクセスキー

手順

1. root dirにて以下のコマンドを実行
terminal
docker exec -it コンテナID sh
/app # EDITOR=vi rails credentials:edit

コンテナIDは以下のコマンドで確認できる

terminal
docker ps
2. credentialsを編集

credentialsをターミナル上で編集する

awsは最初コメントアウトされているので外す (それに気付かずハマってしまいました)

aws:
 access_key_id: 取得したアクセスキー
 secret_access_key: 取得したシークレットアクセスキー

入力し保存する.

Esc→:wqでセーブして保存する.

:wq
3. config/storage.ymlの編集

regionとbucketを作成したものに書き換える.

config/storage.yml
amazon:
 service: S3
 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
 secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
 region: "リージョン"
 bucket: "bucket名"
4. aws-sdk-s3というgemをインストールする

Gemfileを編集

Gemfile
gem 'aws-sdk-s3', require: false

Gemfileを書き換えたので,コンテナをbuildし直す.

terminal
docker-compose build コンテナ名
5. config/environments/development.rbでActive Storageの参照先を:localから:amazonへと変更
config/environments/development.rb
config.active_storage.service = :amazon

以上で,S3のバケットに画像が保存された.

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