Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

attr_memoized

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

attr_memoized

  • 0.3.1
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

:doctype: book :toc:

image:https://travis-ci.org/kigster/attr_memoized.svg?branch=master[Build Status,link=https://travis-ci.org/kigster/attr_memoized] image:https://codeclimate.com/github/kigster/attr_memoized/badges/gpa.svg[Code Climate,link=https://codeclimate.com/github/kigster/attr_memoized] image:https://codeclimate.com/github/kigster/attr_memoized/badges/coverage.svg[Test Coverage,link=https://codeclimate.com/github/kigster/attr_memoized/coverage] image:https://codeclimate.com/github/kigster/attr_memoized/badges/issue_count.svg[Issue Count,link=https://codeclimate.com/github/kigster/attr_memoized]

= AttrMemoized

This is a simple, and yet powerful memoization library, with a specific goal of being thread-safe during lazy-loading of expensive to create attributes. Class method attr_memoized automatically generates attribute reader and attribute writer methods. The reader performs a thread-safe lazy-initialization of each attribute. The writer performs a thread-safe assignment. You can disable writer method generation by using attr_memoized_reader class method instead of the attr_memoized.

This gems provides a shorthand syntax for defining lazy-initialized variables as "one-liners", while additionally providing thread-safety guarantees around lazy-initialization of attributes, or attribute assignments.

[discrete] WARNING: Caveat: If the initialization or assignment returns a "falsey" result (ie, false or nil), then the attribute will attempt to be re-initialized every time its "reader" method is called. This is not a bug. We treat falsey value as uninitialized by design.

== Complete Example

Below we have a Configuration class that has several attributes that are all lazy loaded.

[source,ruby]

require 'redis' require 'attr_memoized'

module Concurrent class RedisConfig

include AttrMemoized

CONTENT_KEY = 'site-content'.freeze

# This imports instance method #with_lock+, and class methods
# #attr_memoized, and #attr_memoized_reader.

attr_memoized_reader :redis_key,    -> { CONTENT_KEY }
attr_memoized_reader :redis_config, -> { { host: 'localhost', port: 6379 } }
attr_memoized_reader :redis,        -> { Redis.new(redis_config) }
attr_memoized_reader :contents,     -> { redis.get(redis_key) }


# #with_lock method if offered in place of the #synchronize
# to avoid double-locking within the same thread.
def reload_config!(new_key)
  with_lock do
    self.redis_key = new_key
    contents(reload: true)
  end
end

end end

@config = Concurrent::RedisConfig.new @config.contents #=> { "host": "127.0.0.1" } @config.reload_config!('another_file') #=> { "host": "google.com" } @config.contents #=> { "host": "google.com" }

=== The Problem

One of the issues with memoization in multi-threaded environment is that it may lead to unexpected or undefined behavior, due to the situation known as a https://stackoverflow.com/questions/34510/what-is-a-race-condition[_race condition_].

Consider the following example:

[source,ruby]

class Account def self.owner # Slow expensive query @owner ||= ActiveRecord::Base.execute('select ...').first end end

Let's be dangerous:

[ Thread.new { Account.owner }, Thread.new { Account.owner } ].map(&:join)

==== Deeper into the ||=

Ruby evaluates a||=b as a || a=b, which means that the assignment above won't happen if a is "falsey", ie. false or nil. If the method self.owner is not synchronized, then both threads will execute the expensive query, and only the result of the query executed by the second thread will be saved in @owner, even though by that time it will already have a value assigned by the first thread, that by that time had already completed.

Most memoization gems out there, among those that the author had reviewed, did not seem to be concerned with thread safety, which is actually OK under wide ranging situations, particularly if the objects are not meant to be shared across threads.

But in multi-threaded applications it's important to protect initializers of expensive resources, which is exactly what this library attempts to accomplish.

== Using attr_memoized

AttrMemoized -- the gem's primary module, when included, decorates the receiver with several useful methods:

  • Pre-initialized class method #attr_memoized_mutex. Each class that includes AttrMemoized gets their own mutex.

  • Pre-initialized instance method #attr_memoized_mutex. Each instance of the class gets it's own dedicated mutex.

  • Convenience method #with_lock is provided in place of #attr_memoized_mutex.synchronize and should be used to wrap any state changes to the class in order to guard against concurrent modification by other threads. It will only use mutex.synchronize once per thread, to avoid self-deadlocking.

  • New class method #attr_memoized is added, with the following syntax:

[source,ruby]

attr_memoized :attr, [ :aliases, ], -> { block returning a value } # A proc attr_memoized :attr, [ :aliases, ], :instance_method, arg1: value, ... # A symbol attr_memoized :attr, [ :aliases, ], SomeClass.method(:method_name) # A method

  • In the above definitions: ** If a Proc is provided as an initializer, it will be called via #instance_exec method on the instance and, therefore, can access any public or private method of the instance without the need for self. receiver.

** If the initializer is a Symbol, it is expected to be an instance method name, of a method that accepts keyword arguments - in other words the methods should always have **opts as the last argument, even if you are not using them. + *** The reason for this is that you can supply arguments to methods when defining lazy initializations, for instance — take a look at the definition of pi25 in the provided example NumericHelper below.

** Finally, any Method instance can also be used.

** Note, that multiple attribute names can be passed to #attr_memoized, and they will be lazy-loaded in the order of access and independently of each other. If the block always returns the same exactly value, then the list may be viewed as aliases. But if the block returns a new value each time its called, then each attribute will be initialized with a different value, eg:

[source,ruby]

Kernel.srand # init random numbers require 'attr_memoized' require 'bigdecimal/math'

class NumericHelper include AttrMemoized attr_memoized :random1, :random2, :random3, -> { rand(2**64) }

attr_memoized :pi, :π # call a class method when accessed

Returns PI as a string with digits.

def self.π(digits: 25) precision = digits result = BigMath.PI(precision) result = result.truncate(precision).to_s result = result[2..-1] # Remove '0.' result = result.split('e').first # Remove 'e1' result.insert(1, '.') end end

rng = NumericHelper.new

each is initialized as it's called, and so they

are all different:

rng.random1 #=> 1304594275874777789 rng.random2 #=> 12671375021040220422 rng.random3 #=> 16656281832060271071

second time, they are all already memoized:

rng.random1 #=> 1304594275874777789 rng.random2 #=> 12671375021040220422 rng.random3 #=> 16656281832060271071

rng.pi #=>

== Installation

Add this line to your application's Gemfile:

[source,ruby]

gem 'attr_memoized'

And then execute:

$ bundle

Or install it yourself as:

$ gem install attr_memoized

== Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to https://rubygems.org[rubygems.org].

== Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/kigster/attr_memoized.

== License

The gem is available as open source under the terms of the http://opensource.org/licenses/MIT[MIT License].

FAQs

Package last updated on 09 Sep 2020

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc