简体   繁体   中英

When do Ruby/Rails class variables get initilalized?

I am currently facing the issue, that all my objects using an instance variable with the "same" inital value and I only read this one.

def initialize
    @available_ids = read_only_value
end

But here comes the tricky thing: This "read_only_value" is retrieved from a API. To make it easy, let's assume the value is an array of ints and gets changed once a month.

But I don't want to DoS the API by retrieving the value every time I create an object. What if I use a class variable instead?

class Foo
   @@available_ids = read_only_value
   def initialize
      @available_ids = @@available_ids //weird but let's go with it
   end
end

I assume this will solve the issue with spamming the API, since it's only execute once the variable gets initialized...

But when does this happen? When I start my Rails application, when I create the first object? Will it ever be updated? Since the value changes once a month how do I get the update?

PS: I guess the best option would be to save the ids in a database and check the ids once in a while or on demand.

It happens when the class is loaded (read: during Rails startup.)

Also, you don't need the class variable. Class instance variable is good enough:

class Foo
   @available_ids = read_only_value
   def initialize
      @available_ids = self.class.instance_variable_get :@available_ids
   end
end

Rails has a built-in cache , so something along these lines should work: (based on the example in the docs)

class Foo
  def available_ids
    Rails.cache.fetch('available_ids', expires_in: 24.hours) do
      API.fetch_ids
    end
  end
end

How about using redis/memcache? Cache the api values in redis/memcache and set expiry to one month or little shorter. In the initializer always pull from cache, and if cache is nil, then call the method that calls the real api and again caches it.

In the case of your code, the API would be accessed when the class gets defined, which means it would be accessed only once, when you start your app. You can see this clearly with this example:

puts "starting app"

def read_only_value
  puts "API accessed!"
  "my value"
end


class Foo
  @@available_ids = read_only_value
  def initialize
    @available_ids = @@available_ids
    puts "initializing with value: #{@available_ids}"
  end
end

puts "initializing two objects: "
Foo.new
Foo.new

#=> starting app
#=> API accessed!
#=> initializing two objects: 
#=> initializing with value: my value
#=> initializing with value: my value

When trying to manage API access, the best practice is to wrap your API in its own class. Doing so lets you keep track of API calls and cache values independently of the objects using the API:

class API

  def self.instance
    @@instance ||= API.new
  end

  def read_only_value
    if @read_only_value.nil? || (Time.now - @cached_time) > 86400 # one day, in seconds
      puts "accessing api"
      @read_only_value = "access api here"
      @cached_time = Time.now
    end
    @read_only_value
  end

end


class Foo

  def initialize
    @available_ids = API.instance.read_only_value
    puts "initializing with value: #{@available_ids}"
  end

end

puts "initializing two objects: "

Foo.new
Foo.new

#=> initializing two objects: 
#=> accessing api
#=> initializing with value: access api here
#=> initializing with value: access api here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM