開発での小ネタ その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
みたいな感じで定義できるっぽい。
こちらの記事を参考にしました。素晴らしい知見に感謝
メソッドキャッシュを用いて少しでも高速化を図る
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) メソッドを用いて処理を簡潔で分かりやすいものに書き換える
ループを取り除いてコレクションクロージャメソッドを使うと、コードをたどりやすくなる。コレクションクロージャメソッドは、コレクションの中を行き来したり、派生コレクションを作ったりするためのインフラストラクチャコードを隠し、ビジネスロジックに集中できるようにしてくれる。
.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