Search code examples
ruby-on-railsrubyclass-variables

Ruby Class variable and on access assignments


I have a module that has multiple class variables. I'm looking for a class level getter implementation that will only instantiate the @@ variable when the class tries to access it like the following

    module MyProducts
      @@products = nil

      def self.get_product(id)
        # i'm looking for a way that the first call on @@products does a find via AR                 like the following
        # @@products = Product.all
        # this module is in the lib directory of a Rails 2.3.5 app
        @@products.find do |prod|
          prod.id.eql?(id)
        end
      end
    end

I'm looking for this to be transparent so that i don't have to modify the whole module. There are about 10 class level variables with similar functions, all the results of an ActiveRecord .find call


Solution

  • Just use the ||= operator. It would evaluate the right expression only if the right part is nil or false

    def foo
      @@any ||= some_value
    end
    

    The first time you call the method it will initialize the variable with the result of some_value, and following calls will return the @@any value with no need of recomputing some_value.

    Update

    Here it's a little script which shows you how to do that. If you execute that you'll see that the method complex_function is called once since the two print statements both returns 1. However from your comment I see that your Product is an active record, so don't use this approach for what your asking for, it will be very inefficient (read the last part of my answer)

    #!/usr/bin/env ruby
    
    module Foo
      def self.products
        @@products ||=complex_function
      end
    
      @@a = 0
    
      def self.complex_function
        @@a += 1
      end
    end
    
    p Foo.products
    p Foo.products
    

    Update end

    However your approach to save Product.all is pretty inefficient:

    • First it will fetch all the products in the database which could consume a lot of memory when you have lot of products
    • Second your iteration code will be much slower than the db when you have a lot of products

    Replace your whole method with a Product.find(id) call.

    If your Product model isn't stored in the db (maybe an ActiveResource) ignore my previous comment.

    You could also take a look to mattr_accessor and this SO question Difference between mattr_accessor and cattr_accessor in ActiveSupport?

    Finally also take a look to this article which explains the above technique called memoization