読者です 読者をやめる 読者になる 読者になる

Yamakichi’s blog

yamakichiの技術ブログ

開発での小ネタ その2

開発での小ネタ その2

普段の開発で得た知見を書き留めておく

YouTubeへのリンクでクエリを付加するロジック

DBにURLが保存してありすでに存在すればそのまま。タグが付いていなければ付加する こちらを参考にしました。

def youtube_url
    uri = URI.parse("https://www.youtube.com/watch?v=WztpnvW4CHA")
    query_hash = {}
    query_hash.merge!(Hash[URI.decode_www_form(uri.query)]) if uri.query.present?
    query_hash.symbolize_keys!.merge!(tag: youtube.opening_bgm_id)
    uri.query = URI.encoded_www_form(query_hash.to_a)
    uri.to_s
end

URI.parseでURLをオブジェクトに変換し、クエリが存在するか判定する if query.present?

存在するならば、URI.decode_www_formを用いてクエリをkeyとvalueのhash形式にしquery_hashに格納する。

文字列(“hoge”)とシンボル(:hoge)が同一な存在とするため、symbolize_keys!を用いて文字列をシンボルに変換します。

.merge!(v: youtube.opening_bgm_id)で付加したいidをmergeする。

mergeメソッドはすでに同じkeyが存在する場合に上書きします。

URI.encoded_www_form(query_hash.to_a)でクエリをuriのqueryに格納して完了!

URI周りを厳密に処理を行いたい場合は以上のようなやり方で操作すると良さげです。

Railsでhttpリクエストをhttpsにリダイレクトさせる

gem "rack-ssl-enforcer"

rack-ssl-enforcerを用います。 Gemをbundle installし、config/environments/** に以下を記述します。

config.middleware.use Rack::SslEnforcer

これでSSL通信に限定ます。 ロードバランサーなどを用いておりヘルスチェックのhttpリクエストは通したいとい場合は以下の記述を追加。

# except: ["/ping"]を追加してsslの制限をexceptすることで解決
config.middleware.use Rack::SslEnforcer, except: ["/ping"]

enumerizeでscope: trueにするとscopeとして使える

class User < ApplicationRecord
  extend Enumerize
  enumerize :sex, :in => [:male, :female], scope: true
  enumerize :status, :in => { active: 1, blocked: 2 }, scope: :having_status
end

User.with_sex(:female)
# SELECT "users".* FROM "users" WHERE "users"."sex" IN ('female')

User.without_sex(:male)
# SELECT "users".* FROM "users" WHERE "users"."sex" NOT IN ('male')

User.having_status(:blocked).with_sex(:male, :female)
# SELECT "users".* FROM "users" WHERE "users"."status" IN (2) AND "users"."sex" IN ('male', 'female')

slackへのアクション通知はslack-notifierがおすすめ

gem "slack-notifier"
notifier = Slack::Notifier.new(Rails.application.config.slack_webhook_url)
notifier.ping(message)

上記で実行するとslack_webhook_urlに対してmessageが送られる。 リッチな通知を実現したい時には以下のようにattachementsを利用すると良い。

message = {
 title: 'タイトル',
 title_link: 'タイトルのリンク',
 text: 'テキスト文',
 image_url: '表示させるサムネイルのURL',
}
SlackNotifier.ping(attachments: [message])

こちらを参考にしました。

バッチなどで大量のデータを操作する際にはfind_eachと良い

分割してレコードを取得して処理をする。 デフォルトで1000件ずつ処理をする。

Railsには find_each というメソッドが用意されています。通常の each メソッドを使用すると、全データをまとめてメモリに展開してから処理を開始します。そのため、十分にメモリに載るデータ量であれば何も問題ないですが、数百万、数千万というデータ量になってくるとメモリに載りきらずに溢れてしまって大変なことになります。 find: 全データをメモリに展開してから処理 find_each: 少しずつデータをメモリに展開しつつ処理 そういうときには find_each メソッドを使いましょう。

参考にしました。 Railsで大量のデータをまとめて更新するならfind_each使うよね

Page.where(:category_id => 1).find_each do |page|
    # hogehoge
end

引数の管理にはハッシュとfetchを使おう

クラス設計で依存関係の管理を行う時のテクニックです。 引数が必要なメソッドを定義した時に値を渡す順番が元から決まっておりそれに沿った形で値を渡さなければいけない状況が多々あります。 もしくは、引数が必要がない場合とある場合を想定したメソッドがあったりします。そんな問題を解決する際には引数のHash化とfetchを用いると良い感じになります。

導入前

class Gear
  attr_reader :chainring, :cog, :wheel 
  def initialize(chainring, cog, wheel)
    @chainring = chainring
    @cog       = cog
    @wheel     = wheel
  end
end

Gear.new(52, 11, Wheel.new(26, 1.5)).gear_inches

導入後

class Gear
  attr_reader :chainring, :cog, :wheel 
  # fetchを使ってデフォルト値を指定している
  def initialize(args={})
    @chainring = args.fetch(:chainring, 40)
    @cog       = args.fetch(:cog, 18)
    @wheel     = args[:wheel]
  end
end

Gear.new(
    chainring: 52, 
    cog:       11,
    wheel:     Wheel.new(26, 1.5)).gear_inches

引数をHashで渡すことにより引数を渡す順番の依存がなくなり、 またfetchメソッドを用いることでデフォルト値を設定することも可能になるのでより柔軟性のあるコードになる。良い!!!

saveメソッドを用いて実行するvalidationを指定する

validatesメソッドのonオプションを用いることで走らせるメソッドを指定できる

validates :title, presence: true, on: :create

こうすれば、createメソッドの時にのみvalidationが走ることになる。 saveメソッドのcontextオプションと一緒に用いることで自由に実行するバリデーションを指定できるようになる。

validates :category, presence: true, on: :publish

@obj.attributes = { title: "hoge", content: "こんてんつ" }
@obj.save(context: :publish)

すごく良い

複数のvalitetionをかける場合は、

with_options on: :publish do |publish|
  publish.validates :title, presence: true
  publish.validates :content, presece: true
end

みたいな感じで定義できるっぽい。

状況によってsave時に実行するバリデーションを切り替える

こちらの記事を参考にしました。素晴らしい知見に感謝

メソッドキャッシュを用いて少しでも高速化を図る

class Hoge
  attr_accessor :obj
  def huga
    @obj ||= begin
      sleep 3
      "aaa"
    end
  end
end

一回目にhogeを実行する時よりも2回目の方が実行が早くなる。@objインスタンス変数に値が存在すればそれを使うという書き方をすることで不必要な処理を省く。 すごく細かいところだかこう小さな気遣いが後々効いてくる。なお、使いどころには注意すること

RSpecでスタブを定義しテストをする

スタブ

テストを行うためにメソッドにダミーの振る舞いを定義すること

  • allowでクラス名を指定
  • receiveでメソッドを指定
  • and_returnでメソッド実行の戻り値を指定
describe "" do
  before do
    allow_any_instance_of(Twitter).to receive(:post).and_return(true)
   end
end

テストを実行する前に 振る舞いを定義しておく

describe "" do
  before do
    allow_any_instance_of(Twitter).to receive(:post).and_raise(ArgumentError)
  end
end

and_raiseで例外エラーを発生させることも可能

コレクションクロージャメソッドのすゝめ

collect (map)、select (filter)、inject (reduce) メソッドを用いて処理を簡潔で分かりやすいものに書き換える

ループを取り除いてコレクションクロージャメソッドを使うと、コードをたどりやすくなる。コレクションクロージャメソッドは、コレクションの中を行き来したり、派生コレクションを作ったりするためのインフラストラクチャコードを隠し、ビジネスロジックに集中できるようにしてくれる。

引用: リファクタリング Ruby エディション

.select フィルタ処理

# before
managers = []
employees.each do |e|
  managers << e if e.manager?
end

# after
managers = employees.select{ |e| e.manager? }

.collect 収集処理

# before
offices = []
employees.each{ |e| offices << e.office }

# after
offices = employees.collect{ |e| e.office }

.select & .collect 複数の処理をまとめて

# before
managerOffices = []
employees.each do |e|
  managerOffices << e.office if e.manager?
end

# after
managerOffices = employees.select{ |e| e.manager? }
                          .collect{ |e| e.office }

gsubメソッド ブロックを用いる

Rubyのgsubメソッドは置換を行うメソッド

第1引数にマッチした値を第2引数の値に置き換える

values = "apple cherry banana"
values.gsub("apple", "pear")
#=> "pear cherry banana"

ブロックを使う方法があることを知った。

"<b>ruby</b> <i>python</i>".gsub(/<.>(.+?)<\/.>/) {"<p>#{$1}</p>"}
# => "<p>ruby</p> <p>python</p>"

$1から$9が特殊変数として定義されているそうな。

Ruby .mapの返り値は必ず配列が返ってくる

.mapというメソッドがあるがこれをハッシュに対して実行した際に、返り値がハッシュではなく配列が返ってきた。 ハッシュの返り値を期待していたもののmapメソッドは必ず配列が返る仕様になっているようで詰まった。 .mapには別名で.collectメソッドも存在する

each_slice each_with_object()

Rubyの配列操作ではメソッドが豊富に用意されている。

.each_sliceメソッドは引数に渡した数ごとに分割した値を返してくれる

.each_with_objectメソッドはinjectメソッドに似ている配列操作内で使用したいオブジェクトをブロック内で使えるようにする LINK