No commit activity in last 3 years
No release in over 3 years
A collection of useful patterns
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

>= 0.0.2, ~> 0.0
 Project Readme

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¶ ↑

  • Check out the latest master to make sure the feature hasn’t been implemented or the bug hasn’t been fixed yet.

  • Check out the issue tracker to make sure someone already hasn’t requested it and/or contributed it.

  • Fork the project.

  • Start a feature/bugfix branch.

  • Commit and push until you are happy with your contribution.

  • Make sure to add tests for it. This is important so I don’t break it in a future version unintentionally.

  • Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.

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