0.0
The project is in a healthy, maintained state
temporarily enabling or disabling boolean attributes on classes and instances
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies
 Project Readme

WithAttributes

Testing

Temporarily enabling or disabling boolean attributes on classes and instances using with and without dynamic methods.

Installation

Add this line to your application's Gemfile:

gem "with_attributes"

And then execute:

bundle install

Usage

class User
  extend WithAttributes

  with_attribute :notifications, default: false
end

This add the following methods:

  • User.with_notifications for enable notifications in class-level during block execution.

  • User.without_notifications for disable notifications in class-level during block execution.

  • User.enable_notificationsfor enable notifications in class-level permanently.

  • User.disable_notificationsfor disable notifications in class-level permanently.

  • User.notifications?for check current status of notifications in class-level, using default value if not inside with_notifications or without_notifications modifiers.

  • user.with_notifications for enable notifications in instance-level during block execution.

  • user.without_notifications for disable notifications in instance-level during block execution.

  • user.enable_notificationsfor enable notifications in instance-level permanently.

  • user.disable_notificationsfor disable notifications in instance-level permanently.

  • user.notifications?for check current status of notifications in instance-level, using class-level value if not inside with_notifications or without_notifications modifiers.

Example using class-level methods:

# using default value
User.notifications? # => false

User.with_notifications do
  User.notifications? # => true

  User.without_notifications do
    User.notifications? # => false
  end

  User.notifications? # => true
end

User.notifications? # => false

Example using instance level methods:

user = User.new

# if notifications is not changed in this instance, using value of class or default if class value is also not changed
user.notifications? # => false

# changing value in class-level
User.with_notifications do
  user.notifications? # => true
end

# changing value in instance-level
user.with_notifications do
  user.notifications? # => true
end

# using nested
User.with_notifications do
  user.without_notifications do
    user.notifications? # => false
  end
end

Real world example

module Trackable
  extend ActiveSupport::Concern

  class_methods do
    include WithAttributes
  end

  included do
    with_attribute :tracking, default: true

    with_options if: :tracking? do
      after_create  { ... }
      after_update  { ... }
      after_destroy { ... }
    end
  end
end

class User
  include Trackable
end

User.without_tracking do
  User.create(...)
end

User.find(...).tap do |user|
  user.without_tracking do
    user.update(...)
  end
end

Thread safety

Thread safety is guaranteed using class-level methods. Instance-level methods only are thread safe if each thread not share the same instance with others.

# thread safe example

t1 = Thread.new do
  User.without_notifications do
    User.create(...) # user created without notifications
  end
end

t2 = Thread.new do
  User.with_notifications do
    User.create(...) # user created with notifications
  end
end

[t1, t2].map(&:join)
# thread safe example

t1 = Thread.new do
  user = User.find(...)

  user.without_notifications do
    user.update(...) # user updated without notifications
  end
end

t2 = Thread.new do
  user = User.find(...)

  user.with_notifications do
    user.update(...) # user updated with notifications
  end
end

[t1, t2].map(&:join)
# thread unsafe example

user = User.new

t1 = Thread.new do
  user.without_notifications do
    user.update(...) # unexpected behaviour, can be created with or without notifications
  end
end

t2 = Thread.new do
  user.with_notifications do
    user.update(...) # unexpected behaviour, can be created with or without notifications
  end
end

[t1, t2].map(&:join)

Licence

Copyright © 2024 Javier Aranda. Released under the terms of the MIT license.