Gem Version

SknUtils

Ruby Gem containing a Ruby PORO (Plain Old Ruby Object) that can be instantiated at runtime with an input hash. This library creates an Object with instance variables and associated getters and setters for Dot or Hash notational access to each instance variable. Additional instance variables can be added post-create by 'obj.my_new_var = "some value"', or simply assigning it.

The intent of this gem is to be a container of data results or value bean, with easy access to its contents with on-demand transformation back to a hash (#to_hash) for easy serialization using standard ruby Hash serialization methods.

  • Transforms the initialization hash into object instance variables, with their Keys as the method names.
  • If the key's value is also a hash, it too can optionally become an Object.
  • if the key's value is a Array of Hashes, each element of the Array can optionally become an Object.

This nesting action is controlled by the value of the options key ':depth'. The key :depth defaults to :multi, and has options of :single, :multi, or :multi_with_arrays

The ability of the resulting Object to be Marshalled(dump/load) can be preserved by merging configuration options into the input params key ':enable_serialization' set to true. It defaults to false for speed purposes

New Features


08/2016  V2.0.3  
Added an exploritory ActionService class and RSpec test, triggered by reading [Kamil Lelonek](https://blog.lelonek.me/what-service-objects-are-not-7abef8aa2f99#.p64vudxq4)
I don't support his approach, but the CreateTask class caught my attention as a Rubyist.        

02/2015  V2.0.2  
Added Jim Gay's Direction module, from his [Eastward Video](http://confreaks.tv/videos/rubyconf2014-eastward-ho-a-clear-path-through-ruby-with-oo)
which allows to use :command instead of :Forwardable to implement a portion of the 'Eastward' methodology.        

12/2015  V2.0  
All references to ActiveRecord or Rails has been removed to allow use in non-Rails environments
as a result serialization is done with standard Ruby Hash serialization methods; by first transforming
object back to a hash using its #to_hash method. 

06/2015  V1.5.1 commit #67ef656
Last Version to depend on Rails (ActiveModel) for #to_json and #to_xml serialization

Configuration Options


Include in initialization hash
  :enable_serialization = false     -- [ true | false ], for speed, omits creation of attr_accessor
  :depth = :multi                   -- [ :single | :multi | :multi_with_arrays ]

Public Methods


Each concrete Class supports the following utility methods:
  #to_hash                       -- returns a hash of all user attributes
  #to_hash(true)                 -- returns a hash of all user and internal attributes -- use for serialization
  #[]                            -- returns value of attr, when #[<attr_name_symbol>]
  #[]=(attr, value)              -- assigns value to existing attr, or creates a new key/value pair
  #<attr>?                       -- detects true/false presence? of attr, and non-blank existance of attr's value; when #address?
  #<attr>                        -- returns value of named attribute
  #<attr> = (value)              -- assigns value to existing attr, or creates a new key/value pair
  -- Where <attr> is a key value from the initial hash, or a key that was/will be dynamically added      

  #depth_level                   -- returns parsing depth level, see :depth
  #serialization_required?       -- returns true/false if serialization is enabled
  #clear_<attr>                  -- assigns nil to existing attr, when #clear_attr

Public Components


Inherit from NestedResultBase or instantiate an pre-built Class:
  SknUtils::ResultBean               # => Not Serializable and follows hash values only.
  SknUtils::PageControls             # => Serializable and follows hash values and arrays of hashes.
  SknUtils::GenericBean              # => Serializable and follows hash values only.
  SknUtils::ValueBean                # => Serializable and DOES NOT follows hash values.
or Include AttributeHelpers          # => Add getter/setters, and hash notation access to instance vars of any object.

Basic features include:

 - provides the hash or dot notation methods of accessing values from object created; i.e
     'obj = SknUtils::ResultBean.new({value1: "some value", value2: {one: 1, two: "two"}}) 
     'x = obj.value1' or 'x = obj.value2.one'
     'x = obj["value1"]'
     'x = obj[:value1]'

 - enables serialization by avoiding the use of ''singleton_class'' methods, #attr_accessor, which breaks Serializers:
    Serializer supports #to_hash, and standard Marshall''ing.  Notice use of #to_hash to convert object back to a Ruby Hash before
    using #to_json and #to_xml; presumed to be methods enabled on the standard Ruby Hash class.

    person = SknUtils::PageControls.new({name: "Bob"})
    person.to_hash             # => {"name"=>"Bob"}
    person.to_hash.to_json     # => "{\"name\":\"Bob\"}"
    person.to_hash.to_xml      # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<page-controls>\n  <name>Bob</name>\n</page-controls>\n"
    dmp = Marshal.dump(person) # => "\x04\bo:\x1ASknUtils::PageControls\x06:\n@nameI\"\bBob\x06:\x06ET"
    person = Marshal.load(dmp) # => #<SknUtils::PageControls:0x007faede906d40 @name="Bob">

    ***GenericBean designed to automatically handles the setup for serialization and multi level without arrays 

 - post create additions:
    'obj = SknUtils::ResultBean.new({value1: "some value", value2: {one: 1, two: "two"}}) 
    'x = obj.one'                          --causes NoMethodError
    'x = obj.one = 'some other value'      --creates a new instance value with accessors
    'x = obj.one = {key1: 1, two: "two"}'  --creates a new ***bean as the value of obj.one
    'y = obj.one.two'                      --returns "two"
    'y = obj.one[:two]                     --returns "two"
    'y = obj.one['two']                    --returns "two"

 - supports predicates <attr>? and clear_<attr>? method patterns:   
    'obj = SknUtils::PageControls.new({name: "Something", phone: "2609998888"})'
    'obj.name?'       # => true    true or false, like obj.name.present?
    'obj.clear_name'  # => nil     sets :name to nil

The combination of this NestedResultBase(dot notation class) and AttributeHelpers(hash notation module), produces these effects given the same params hash:

drb = SknUtils::ResultBean.new(params)                          Basic dot notation: effect of :depth
----------------------------------------------------      -----------------------------------------------------------------

(DOES NOT FOLLOW Values) :depth => :single

    * params = {one: 1,                                         drb.one      = 1
                two: { one: 1,                                  drb.two      = {one: 1, two: 'two}
                     two: "two"                               drb.two.two  = NoMethodError
                     }, 
                three: [ {one: 'one', two: 2},                  drb.three    = [{one: 'one', two: 2},{three: 'three', four: 4}]
                         {three: 'three', four: 4}              drb.three[1] = {three: 'three', four: 4}
                       ]                                        drb.three[1].four = NoMethodError
               }      

(Follow VALUES that are Hashes only.) :depth => :multi

    * params = {one: 1,                                         drb.one      = 1
                two: { one: 1,                                  drb.two      = <SknUtils::ResultBean>
                           two: "two"                               drb.two.two  = 'two'
                     }, 
                three: [ {one: 'one', two: 2},                  drb.three    = [{one: 'one', two: 2},{three: 'three', four: 4}]
                         {three: 'three', four: 4}              drb.three[1] = {three: 'three', four: 4}
                       ]                                        drb.three[1].four = NoMethodError
               }

(Follow VALUES that are Hashes and/or Arrays of Hashes) :depth => :multi_with_arrays

    * params = {one: 1,                                         drb.one      = 1
                two: { one: 1,                                  drb.two      = <SknUtils::ResultBean>
                       two: "two"                               drb.two.two  = 'two'
                     }, 
                three: [ {one: 'one', two: 2},                  drb.three    = [<SknUtils::ResultBean>,<SknUtils::ResultBean>]
                             {three: 'three', four: 4}                drb.three[1] = <SknUtils::ResultBean>
                       ]                                        drb.three[1].four = 4
               }      

Usage:

(DOES NOT FOLLOW Values)

       class SmallPackage < SknUtils::NestedResultBase
          def initialize(params={})
            super( params.merge({depth: :single}) )    # override default of :multi level
          end
       end

(Follow VALUES that are Hashes only.)

       class MyPackage < SknUtils::NestedResultBase
          # defaults to :multi level
       end

    -- or --

       class MyPackage < SknUtils::NestedResultBase
          def initialize(params={})
            # your other init stuff here
            super(params)    # default taken 
          end
       end

    -- or --

       class MyPackage < SknUtils::NestedResultBase
          def initialize(params={})
            # your other init stuff here
            super( params.merge({depth: :multi}) )    # Specified
          end
       end

    ** - or -- enable serialization and default to multi
       class MyPackage < SknUtils::NestedResultBase
          def initialize(params={})
            super( params.merge({enable_serialization: true}) )    # Specified with Serialization Enabled
          end
       end

(Follow VALUES that are Hashes and/or Arrays of Hashes, and enable Serializers)

       class MyPackage < SknUtils::NestedResultBase
          def initialize(params={})
            super( params.merge({depth: :multi_with_arrays, enable_serialization: true}) )    # override defaults
          end
       end

NOTE: Cannot be Marshalled/Serialized unless input params.merge(true) -- default is false Use GenericBean or PageControls if serialization is needed, they initialize with this value true.

Installation


runtime prereqs: V2+ None V1+ gem 'active_model', '~> 3.0'

Add this line to your application's Gemfile:

gem 'skn_utils'

And then execute:

$ bundle

Or install it yourself as:

$ gem install skn_utils

Build

  1. $ git clone [email protected]:skoona/skn_utils.git
  2. $ cd skn_utils
  3. $ gem install bundler
  4. $ bundle install
  5. $ bundle exec rspec
  6. $ gem build skn_utils.gemspec
  7. $ gem install skn_utils
  8. Done

Console Workout

Start with building gem first.

$ cd skn_utils
$ bundle exec pry
[1] pry(main)> require 'skn_utils'    
[2] pry(main)> rb = SknUtils::ResultBean.new({sample: [{one: "one", two: "two"},{one: 1, two: 2}] })
[3] pry(main)> pg = SknUtils::PageControls.new({sample: [{one: "one", two: "two"},{one: 1, two: 2}] }) 
[4] pry(main)> pg.sample.first.one          # Tip :multi_with_arrays
[5] pry(main)> rb.sample.first.one          # Tip :multi without arrays you will get a NoMethodError
[6] pry(main)> rb.sample.first[:one]        

[n] pry(main)> exit
* Done

Contributing

  1. Fork it
  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

License

MIT. See LICENSE.