ActiveSupport::PerThreadRegistryでThread.currentより安全にグローバルな値を保持する╭( ・ㅂ・)و ̑̑

Railsでグローバルデータを保持したいときに、Thread.currentを使えば簡単ですよね

Thread.current[:current_user] = user
Thread.current[:current_user] #=> user

僕も気軽に使ってましたが、最近こんな記事を見つけました。

Better globals with a tiny ActiveSupport module - Weissblog

Thread.currentを使わない方が良い理由

  • キーがかぶってしまったらデータを上書きされるで!
  • 構造化されてないで!

ActiveSupport::PerThreadRegistryを使うと同じcurrent_userでもデータを担保できます

class RequestRegistry
  extend ActiveSupport::PerThreadRegistry
  attr_accessor :current_user
end

class PhoneNumberApiRegistry
  extend ActiveSupport::PerThreadRegistry
  attr_accessor :current_user
end

RequestRegistry.current_user = user1
RequestRegistry.current_user #=> user1

PhoneNumberApiRegistry.current_user = user2
PhoneNumberApiRegistry.current_user #=> user2

てな感じです

ここらを参考にすれば良いみたいです

rails/runtime_registry.rb at master · rails/rails · GitHub

rails/explain_registry.rb at master · rails/rails · GitHub

Happy Hacking٩( ‘ω’ )و

ActiveSupport::OrderedOptionsを使ってHashで.(ドット)を使えるようにする٩( ‘ω’ )و

もはやActiveSupportなしでは生きていけない僕

Hashをイイ感じに拡張してるActiveSupport::OrderedOptionsです

github.com

# activesupport/lib/active_support/ordered_options.rb
module ActiveSupport
  class OrderedOptions < Hash
    ...
  end
end

普通のHashだとこんな感じ

hash = {}
hash[:name] = 'murajun1978'

hash[:name] #=> 'murajun1978'

ActiveSupport::OrderedOptionsを使うとこうなる

hash = ActiveSupport::OrderedOptions.new
hash.name = 'murajun1978'

hash.name   #=> 'murajun1978'
hash[:name] #=> 'murajun1978'

.(ドット)でvalueを取得できるようになりましたーヾ( ̄∇ ̄=ノ

チェインしたいときはこんなかんじ

hash = ActiveSupport::OrderedOptions.new
hash.user =  ActiveSupport::OrderedOptions.new
hash.user.name = 'murajun1978'

hash.user.name #=> 'murajun1978'

既存のHashを使いたいときはActiveSupport:: InheritableOptionsを使う

hash = ActiveSupport:: InheritableOptions.new({name: 'murajun1978'})

hash.name #=> 'murajun1978'

I18nのconfigを定義するとこで使われてるねー

github.com

module I18n
  class Railtie < Rails::Railtie
    config.i18n = ActiveSupport::OrderedOptions.new
    config.i18n.railties_load_path = []
    config.i18n.load_path = []
    config.i18n.fallbacks = ActiveSupport::OrderedOptions.new

    ...
  end
end

次はThreadについて書くよてい(多分、明日の神戸.rb Meetup #15で)

Happy Hacking٩( ‘ω’ )و

Array#extract_options!で引数からオプションをひっこぬく٩( ‘ω’ )و

Railsでのvalidationを設定する時はこう書きますよね?

validates :name, :kana,
  presence: true

validatesメソッドに、symbolとhashが複数指定されています

オプション部分はHashですねー

引数とオプション(Hash)を分離したいときに大活躍するのがArray#extract_options!です

extract_options!のコードを見てみましょう

# activesupport/lib/active_support/core_ext/array/extract_options.rb
class Array
  def extract_options!
    if last.is_a?(Hash) && last.extractable_options?
      pop
    else
      {}
    end
  end
end

last.is_a?(Hash) && last.extractable_options?で配列の末尾のオブジェクトがHashかチェックしてます

HashだったらpopメソッドでHashを引っこ抜いてます

Hashでなかったら空のHashを返してます

メソッド!がついてるのは破壊的メソッドだからですね

Railsコンソールで確認してみましょう

args = [:name, :kana, {presence: true}]
options = args.extract_options!

args    #=> [:name, :kana]
options #=> {:presence=>true}

おおー、引数とoption(Hash)を分離できましたヾ( ̄∇ ̄=ノ

validatesのコードはこんなかんじ( ˘ω˘)

# activemodel/lib/active_model/validations/validates.rb
def validates(*attributes)
  defaults = attributes.extract_options!.dup
  [...]
end

extract_options!のコードを見ればわかりますが、Hashは末尾に指定しないとダメなので気を付けよう

[:name, {presence: true}, :kana].extract_options! #=> {}

Happy Hacking٩( ‘ω’ )و

Enumerable#partitionでブロックの評価が真と偽の要素に分ける٩( ‘ω’ )و

いままでブロックで評価して真のものはselectで、それ以外はrejectとか2回やってた...

でも、これ1回でできたらおしゃれですよねー

そんなときはEnumerable#partition

こんなかんじ( ˘ω˘)

array = ["cat", 1, 2, "dog", 0.1]

numbers, non_numbers = array.partition { |v| v.is_a?(Numeric) }

numbers     #=> [1, 2, 0.1]

non_numbers #=> ["cat", "dog"]

Happy Hacking٩( ‘ω’ )و

Adequate Recordでキャッシュされないケースヘ(^o^)ノ

第65回 Ruby関西 勉強会に参加したよヘ(^o^)ノのつづき

スライドに、Adequate Recordでキャッシュされないケースを書いたけど、 ざっくりしすぎなのでまとめてみる( ˘ω˘)

find_byを例にactiverecordのコードを読みながら確認していく

コードはこんな感じ(コメントの*1とかは説明しやすいのでつけてるだけ)

# activerecord/lib/active_record/core.rb
def find_by(*args) # :nodoc:
  # *1  
  return super if current_scope || !(Hash === args.first) || reflect_on_all_aggregations.any?
  # *2  
  return super if default_scopes.any?

  hash = args.first

  # *3
  return super if hash.values.any? { |v|
    v.nil? || Array === v || Hash === v
  }

  # We can't cache Post.find_by(author: david) ...yet
  # *4  
  return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }

  ...
end

*1で判定していること

current_scopeがある

current_scopeでスコープがないことを確認してる

引数がHash以外

!(Hash === args.first)でチェックしてる

こんなのはキャッシュしてくれない

Post.find_by('created_at < ?', 1.weeks.ago)
composed_ofが設定されている

reflect_on_all_aggregations.any?でチェックしてる

設定されているだけでキャッシュしてくれない


*2で判定してること

default_scopeが設定されている

設定されているだけでキャッシュしてくれない


*3で判定していること

引数のハッシュ値(Value)がnil, Array,Hash

こんなのはキャッシュしない

Post.find_by(title: nil)
Post.find_by(title: ['ruby', 'rails'])
# Hashがくるケースが思い浮かばない...


*4で判定していること

Hashのキーにモデルのカラム以外が指定されている

以上の条件に当てはまる場合に、superすなわち本家のfind_byをよんでます

なので、単一テーブル継承(STI)やPolymorphicは、設定されているだけではキャッシュの対象外とはなりません

ここがややこしそうだ。。。

間違えてたら教えてくださいねー

Happy Hacking٩( ‘ω’ )و

神戸.rb Meetup #13に参加したよヘ(^o^)ノ

神戸.rb Meetup #13に参加しましたー

毎回思うがホント勉強になる

僕の思いもみんなに伝えられたし良かった

今日はActiveSupport::StringInquirerについて調べた

結構こんなの書いちゃいがちですよね?

if Rails.env == 'production'
  ...
end

それが、こう書ける

if Rails.env.production?
  ...
end

これを使ってこんなの実装してみた

priority_typeってフィールドがあって、'low'や'high'なんかの文字列がセットされてるとする

class Task < ActiveRecord::Base
  def priority
    priority_type.inquiry
  end
end

lowかどうかチェックしてみる

task.priority_type    #=> "low"
task.priority.low?    #=> true
task.priority.middle? #=> false

==で判定するよりグッと意図が伝わりやすくなってると思う(多分...

Happy Hacking٩( ‘ω’ )و

RubyのHashで要素数を取得するヘ(^o^)ノ

Hashで要素の数を取得してみる

Hash#lengthとHash#size

favorites_language = {
  bob: 'Ruby',
  jone: 'Ruby',
  tiger: 'Perl'
}
favorites_language.length #=> 3
favorites_language.size   #=> 3

Enumerable#count

favorites_language = {
  bob: 'Ruby',
  jone: 'Ruby',
  tiger: 'Perl'
}
favorites_language.count #=> 3

では、Valueが'Ruby'である要素の数を取得してみる

Hash#lengthとHash#size

favorites_language = {
  bob: 'Ruby',
  jone: 'Ruby',
  tiger: 'Perl'
}
favorites_language.select{|k, v| v == 'Ruby'}.size #=> 2

Enumerable#count{|obj| ...}

favorites_language = {
  bob: 'Ruby',
  jone: 'Ruby',
  tiger: 'Perl'
}
favorites_language.count{|key, v| v == 'Ruby'} #=> 2

そう!countにはブロックを渡せるのだー(今までマジで知らんかった...

もう少しおしゃれにしてみよう

Enumerable#count(item)

favorites_language = {
  bob: 'Ruby',
  jone: 'Ruby',
  tiger: 'Perl'
}
favorites_language.values.count('Ruby') #=> 2

まだまだ知らんこといっぱいですわー

がんばろっと

Happy Hacking٩( ‘ω’ )و