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:
-
load all the .rb files in the directory/subdirectories
-
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¶ ↑
Copyright © 2013 Bryce Anderson. See LICENSE.txt for further details.