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

attr_magic

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

attr_magic

  • 0.1.2
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

The tools to ease lazy attribute implementation

Overview

Этот текст можно прочитать на русском языке: README-ru.md.

A lazy attribute is an accessor method that performs some computation the first time it is called, memoizes the result into an instance variable, and returns the result. Example:

class Person
  # @return [String]
  attr_accessor :first_name, :last_name

  attr_writer :full_name, :is_admin

  def initialize(attrs = {})
    attrs.each { |k, v| public_send("#{k}=", v) }
  end

  # @return [String]
  def full_name
    @full_name ||= begin
      [first_name, last_name].compact.join(" ").strip
    end
  end

  # @return [Boolean]
  def is_admin
    return @is_admin if defined? @is_admin
    @is_admin = !!full_name.match(/admin/i)
  end
end

Methods #full_name и #is_admin are lazy attributes.

What does AttrMagic provide?

For classes that have lazy attributes, AttrMagic provides:

  1. #igetset and #igetwrite for simple memoization of any values, including false and nil.
  2. #require_attr to validate attributes which are required by the given computation.

Setup

Add to your project's Gemfile:

gem "attr_magic"
#gem "attr_magic", git: "https://github.com/dadooda/attr_magic"

Usage example

To use feature, let's load it into a class:

class Person
   AttrMagic.load(self)
   …
end

AttrMagic methods are now available in the Person class. Let's see how we can use them.

#igetset

In the example above, method #full_name memoizes the result with the ||= operator. This suits us, because the result of the computation is a string.

As to #is_admin, its much more verbose: the result can be false, thus operator ||= won't work.

Both methods can be written using #igetset:

class Persondef full_name
    igetset(__method__) { [first_name, last_name].compact.join(" ").strip }
  end

  def is_admin
    igetset(__method__) { !!full_name.match(/admin/i) }
  end
end

Now our methods are short, uniform, and easy to read.

Also, the computation in #is_admin is clearly visible, whereas previously it was obscured by repetitions of is_admin inside the method already named by this word.

#require_attr

In the example above, method #first_name returns an empty string even if attributes first_name and last_name are unassigned or blank.

Such behavior cannot be considered completely sane. Most likely, the result of #full_name will be displayed in the info block about a person or be used when addressing a person. An empty string, even “legitimately” computed, may cause confusion.

Of course, it's not the Person instance's fault that neither first_name nor last_name were assigned values prior to calling #full_name. Garbage in — garbage out.

However, rather than behaving deliberately “harmful” by returning inarticulate blankness, #full_name could be helpful by signalling about this situation and helping us tackle it promptly.

Suppose we decided, that in order to compute #full_name properly, we at least need a non-blank first_name. An implementation could look like this:

class Persondef full_name
    igetset(__method__) do
      require_attr :first_name
      [first_name, last_name].compact.join(" ").strip
    end
  end
end

Let's see how it works:

Person.new.full_name
# RuntimeError: Attribute `first_name` must not be nil: nil

Not bad! Instead of getting dumb blankness, we've got an exception pointing straight at the reason: unassigned first_name. However, the nil phrasing might sound a bit confusing when talking about string values. Also, what if first_name is assigned, but is empty or blank? Let's see:

Person.new(first_name: "").full_name
# => ""

Person.new(first_name: " ").full_name
# => ""

Our require_attr doesn't react to an empty/blank string yet, despite our decision to ensure that first_name is non-blank. Let's tune the code a bit:

# We need this for `Object#present?`.
require "active_support/core_ext/object/blank"

class Persondef full_name
    igetset(__method__) do
      require_attr :first_name, :present?
      #require_attr :first_name, :not_blank?   # Also possible.
      [first_name, last_name].compact.join(" ").strip
    end
  end
end

Let's try it again:

Person.new.full_name
# RuntimeError: Attribute `first_name` must be present: nil

Person.new(first_name: " ").full_name
# RuntimeError: Attribute `first_name` must be present: " "

Person.new(first_name: "Joe").full_name
# => "Joe"

Now the message is more meaningful and the requirement is met.

We've learned that #require_attr makes it possible to perform a trivial validation of another attribute, needed for a given computation to succeed.

#igetwrite

Method #igetset, described above, operates instance variables directly: checks for being defined, gets and sets. This is sufficient in most cases.

Sometimes, however, proper setting of an attribute demands calling its write accessor, such as #name=.

In such cases, #igetwrite comes in handy. After performing the computation, it saves the value to the object by calling a write accessor.

The product is free software distributed under the terms of the MIT license.

— © 2017-2023 Alex Fortuna

FAQs

Package last updated on 28 Jan 2024

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