Project

renewable

0.0
No commit activity in last 3 years
No release in over 3 years
Provides frozen by default objects for more Functional style.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

 Project Readme

Renewable

Gem Version Code Climate Build Status Test Coverage

Renewable provides means to have objects that are frozen by default. The benefit of this is that it pushes the developer in the direction of a more Functional style of programming that is safer in multi-threaded situations, but maintains many of the benefits of Object-Oriented Programming's binding of data and behavior by simply doing away with in-place mutation.

STATUS

This code is still considered experimental in nature. It is not advised to use this library in production environments without having read and fully comprehended how the code works.

YOU HAVE BEEN WARNED!

Installation

Add this line to your application's Gemfile:

gem 'renewable'

And then execute:

$ bundle

Or install it yourself as:

$ gem install renewable

Usage

Renewable exposes two usage approached: Mixin and Inheritance.

class Person
  include Renewable

  attr_reader :name, :birth_date

  def age
    Date.today.year - birth_date.year
  end
end
class Person < Renewable::Object
  attr_reader :name, :birth_date

  def age
    Date.today.year - birth_date.year
  end
end

Regardless of which you choose, the behavior is the same. Because of how Renewable is intended to function there are some important rules to follow when building your classes with Renewable.

Initializers

Don't write them! Renewable implements an initializer that you can hook into via callbacks. Because of the steps that the initializer takes need to be preserved for everything to work properly you should simply avoid writing your own. All of the callbacks described below will receive an attributes and an options Hash, which are the two arguments the Renewable-supplied initializer expects. Callbacks should simply be defined as protected or private methods based on your inheritance needs.

class Person < Renewable::Object
  attr_reader :name, :birth_date

  private

  def process_arguments(attributes, options)
    # This callback is invoked before Renewable does anything itself. So, do any
    # custom manipulation or processing of the attributes and options hashes you
    # need. This method should return an like so:
    return attributes, options
  end

  def before_freeze(attributes, options)
    # This callback is invoked immediately before Renewable freezes the object's
    # state. Prior to this step Renewable places each item in `attributes` into
    # its respective instance variable and stores `options` as well. This is
    # your last chance to modify the object's internal state. This method should
    # return like so:
    return attributes, options
  end

  def after_freeze(attributes, options)
    # This callback is invoked immediately after Renewable freezes the object's
    # state. At this point you can no longer mutate the object, but this
    # callback is provided just in case you find a use case. It is also the last
    # thing invoked by Renewable's initialize implementation and thus requires
    # no return value.
  end
end

Mutators (Methods that change stuff)

Don't write them! This is where the idea of being more Functional comes into play. Methods that modify the internal state of an object make it harder to ensure the thread-safety of your code. But, so much of Object-Oriented Programming is directly tied to mutation. Thankfully, with Renewable objects that functionality is explicitly not available.

Instead, Renewable provides a method called renew that allows you to achieve functionality similar to mutation, but without the same side effects. Instead renew returns a completely new instance of the object, and you get control over exactly what aspects of internal state change for the new instance.

The major downside to this is that you have to expect your mutator methods to return a new instance, never just a value or nil. But, that's a pretty easy idea to get used to.

#renew

The renew method is what you use to move beyond your frozen object, it accepts two hashes: attributes and options. In that way, it is like calling new, but it starts with the state of your frozen object and applies the changes you specify.

class Person
  #...

  def change_name(name)
    self.renew(name: name.to_s)
  end
end

That really is as simple as renew is in many cases. Because it accepts the attributes and options arguments and calls initialize under the hood you still get all the functionality of the callback handling when renewing objects.

Contributing

  1. Fork it ( https://github.com/[my-github-username]/renewable/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request