Class variables not classy!

(actually)

Class variables caches are not so classy

(umm, in fact)

Class variables are, well, controversial,

class variables used for caching = bad

 

Class variables bad?

Main Reason

class A
  @@x = 2  

  def say
   @@x
  end
end

class B < A
  def haha=y
   @@x=y
  end
end

class C < A
  def haha=y
    @@x=y
  end
end

b = B.new
c = C.new

b.say #=> 2
c.say #=> 2

b.haha = 'because I can'
c.say #=> 'because I can'

Other reasons

  • Thread safety
  • Its a global variable

But still,

they are useful.

Letsbe practical

And my code has no threads !

class A

  @@a_variable_shared_across_all_instances_of_A = nil
  

  def my_method
    # and it is accessible within the scope
    # just like that
    @a_variable_shared_across_all_instances_of_A = 'shared-data'
  end

end

So whats not classy?

class Shippping

  @@countries = Country.all

  def label_the_parcel(address)
    country = @@countries.detect{|name| name == address.country_name}
    print_label country.shipping_code
  end

end

So whats not classy?

class Shippping

  @@california_code = State.where(country: 'U.S.A').last.shipping_code

  def label_for_california(address)
    print_label address
    print_label @@california_code
  end

end

This can fail during class load

This can fail rails s

So whats not classy?

class Shippping

  def label_for_california(address)
    print_label address
    print_label Shipping.california_code
  end

  def self.california_code
    @@california_code ||= State.where(country: 'U.S.A').last.shipping_code
  end
end

The instance-variable cache pattern

Because why not

So whats not classy?

class A

  def slowly_loaded_data
   @slowly_loaded_data ||= slow_queries
  end

end

The actual instance-variable cache pattern

So whats not classy?

class Shippping

  def label_for_california(address)
    print_label address
    print_label Shipping.california_code
  end

  @@california_code = nil
  def self.california_code
    @@california_code ||= State.where(country: 'U.S.A').last.shipping_code
  end
end

That, right there, is a cache

So whats not classy?

And caches have to expire sometime.

Some day, the california_code will change.

So for that day, we have -

 

 

So whats not classy?

class Shippping

  @@california_code = nil
  def self.california_code
    @@california_code ||= State.where(country: 'U.S.A').last.shipping_code
  end

  def self.flush_california_code
    @@california_code = nil
  end
end
class State
  after_save :flush1

  def flush1
    Shipping.flush_california_cache if name=='california'
  end
end

So whats not classy?

And I have no threads

What could possibly go wrong?

 

So whats not classy?

drum-roll...

Enter Sidekiq

  • Cache invalidation will not be carried forward
  • The flush1 action will occur only in the web (Unicorn), and not in the Sidekiq
  • In fact it will not occur in other unicorn workers too
  • It is an in-process cache

What can be done?

  • In-process caches can cause subtle bugs
  • Using Rails.cache with memcache or File cache (default) is safer
  • Instance variable caches are still ok

Who else does it



cd discourse
ag @@ . | wc -l  #=>


cd gitlab
ag @@ . | wc -l  #=>


cd spree
ag @@ . | wc -l  #=>


cd rails
ag @@ . | wc -l  #=>

@h6165

Thanks

Class variables not so classy

By Abhishek Yadav

Class variables not so classy

  • 1,123