fun_with_patterns =

A collection of patterns that I seem to keep re-implementing. I’ll be adding them here as I find time to revise and refactor. Until then, the only ones I’ve gotten around to are…

Loader Pattern ==

The Loader pattern is a good way to customize/modularize part of your app’s functionality.

It assumes that you have a directory or set of directories, which include a bunch of definitions of various ‘thingies’ in different files. For each directory you request loaded, it will look for these ‘thingies’ and try to load each file. You need only define a load_item() and an <optional> loader_pattern_register_item() method. load_item() can simply call load( the_ruby_file.rb ). If you don’t define a load_item() method, it will assume the default case: there is ruby code in that thar file, and I’m gonna execute it.

Say you have a bunch of tasks, scattered over three directories, written in a task-describing DSL, one task per file:

my_lib/builtin/tasks/mysql/backup.db.rb
my_lib/builtin/tasks/website/maintain.users.rb
~/.config/mylib/tasks/email_wendy.rb
~/.config/mylib/tasks/backup_pr0n.rb
~/project/tasks/submit_invoice.rb

Inside each file would be some code describing the task:

Task.new( "backup:db" ) do |t|
    t.run_schedule :daily
    t.action "mysqldump -u{{user}} -h{{host}} {{db}}"
    t.user "dbuser"
    t.host "mysql.hostapalooza.com"
    ... you get the idea
end

The tasks could number in the hundreds. You want your Task class to have a way of loading them, registering them so you can find them, and so on, so you can run them.

Task might look like this:

class Task
  include FunWith::Patterns::Loader

  alias :loader_pattern_registry_key :name

  def initialize( name )
    @name = name           # in the example, it was "backup:db"
  end
end

To load your tasks, you’d run:

Task.loader_pattern_load_from_dir( "mylib/builtin/tasks" )
Task.loader_pattern_load_from_dir( "~/.config/mylib/tasks" )
Task.loader_pattern_load_from_dir( "~/project/tasks" )

or

Task.loader_pattern_load_from_dir( "mylib/builtin/tasks", "~/.config/mylib/tasks", "~/project/tasks" )

And to snag the backup.db task, you’d just say:

task = Task.loader_pattern_registry_lookup( "backup:db" )
task.run

For each directory you give it, it will:

  1. load all the .rb files in the directory/subdirectories

  2. register them into the registry.

You can get some interesting behavior by overwriting individual methods. For example, loading individual configurations from YAML or JSON or XML. There’s an example in the test/ folder.

The default “style” of loading a file is “eval the contents and return the result.” This may not always be the desired behavior. Other built in loading styles:

Note: If you want to manage your own registry by redefining loader_pattern_load_from_dir( dir ), loader_pattern_register_item( item ), etc., make sure you handle and report exceptions.

More example code!

class Klass

  include FunWith::Patterns::Loader
  loader_pattern_configure( :bracketwise_lookup, 
                            :warn_on_key_change, 
                            { :verbose => true, :load_style =>  } )
end

loader_pattern_configure is a quickie method for setting up a variety of behaviors. Options so far:

:bracketwise_lookup  : instead of calling Klass.loader_pattern_registry_lookup( "string" )
                       you can just call Klass["string"]

{:key => :<sym>}     : loader determines registry key by calling this method.  If no key is provided, then
                       it infers a key by using "directory:subdirectory:filename_without_extension"

:warn_on_key_changes : If a newly loaded item has an existing registry key, it prints a warning as it overwrites

:dont_warn_on_key_changes : (default)
:style => :(eval|instance_exec|yaml)  : Use one of the built-in loading styles.  Otherwise, set custom loading
                                        behavior by defining your own Klass.loader_pattern_register_item

The styles need some explanation:

:eval (default)  : evals the contents of the file and returns the result

:instance_exec   : The file contents will run as though inside the object

                   Klass.new do
                     # ----- file contents for boris.rb starts here
                     self.name "Boris"
                     self.age 23
                     self.kill_count 9
                     # ----- file contents for boris.rb end here
                   end

:yaml              The file contents describe a YAML hash, with the topmost keys describing
                   the setter to call in order to set the attribute to that value.  For example,

                   ---
                   - name: Boris

                   would end up calling klass_object.name=( "Boris" )

    <A MODULE>       : Extends the target with the provided module.  For example,

                       module RandomLoaderStyle
                                                 def loader_pattern_load_item( file )
                                         self.loader_pattern_rescue_failing_item_load( file ) do
                                           obj = WeirdObject.factory( :random_seed => file.read )

                                           return obj
                     end
                                           end
                 end

                 class RandomObjects

                 end

GetAndSet ==

When active, this module behaves a lot like attr_accessor.

The difference is, only one method is defined. Whether you’re looking up the instance_variable’s value or altering it depends on whether you pass it an argument.

FunWith::Patterns::GetAndSet.activate

class A
  get_and_set( :radio, :radius, :radium )
  get_and_set( :radiate )
  get_and_set_boolean( :stringy, :flurmish )    # unless changed (! called or @var set some other way), defaults to false (stringy! returns false, not_stringy! returns true)
 end

a = A.new

a.radius( 3.14159 )    # ==> 3.14159
a.radius()             # ==> 3.14159

a.radium()             # ==> nil
a.radium( "radioactive" )  # ==> "radioactive"
a.radium( "Madame Curie")  # ==> "Madame Curie"
a.radium()                 # ==> "Madame Curie"

a.stringy!     # ==> true
a.stringy?     # ==> true
a.not_stringy! # ==> false
a.stringy?     # ==> false

a.flurmish?    # ==> false     # doesn't have a default, and hasn't had one of the !s run
a.not_flurmish? # ==> true   # unfortunate implementation side-effect.  Do not rely on it.

That’s all you need to start using get_and_set( :varname1, :varname2 )

If you want to make all objects more extensible, you can make it available for all objects, not just classes and modules:

FunWith::Patterns::GetAndSet.activate( Object )

class A
end

a = A.new

a.get_and_set( :radiate )
a.get_and_set_boolean( :stringy? )

They work the same, but only apply to the object, not all objects of that class.

Contributing to fun_with_patterns

Copyright © 2013 Bryce Anderson. See LICENSE.txt for further details.