Inherited resourcing and the power of subclassing
27 November 2008
In this edition of PoolParty internals, I’m going to show you how the basic resourcing is done. Virtual resourcing is out of the scope of this blog post, but we’ll come back to that on another blog post.
If you are not familiar with PoolParty, I urge you to check it out
From within PoolParty, resources are a cornerstone. Most all the resources implemented in PoolParty have the same basic structure of each other, but require different parameters. For instance, a file resource can have content associated with it, while an exec cannot. However, they both behave like each other in the context of usage.
Most of the resource classes in PoolParty are tiny. For instance, the Host resource is 4 lines long without a method defined.
module PoolParty
module Resources
class Host < Resource
default_options({
:name => "$hostname",
:ip => "$ipaddress"
})
end
end
end
When the following code is in the pool.spec, the a new host resource is created (we’ll see later that it means a new instance of the Host class is instantiated) and stored in the containing class.
has_host(:name => "orca", :ip => "67.125.84.125")
# Note that this is the same as calling
host(:name => "orca", :ip => "67.125.84.125")
Almost all of the resources implementation are that small. So how do we do this?
def self.inherited(subclass)
end
Because we already use the method_missing to create methods from on the cloud to keep our DSL clean, we will use inherited to create the methods we want on our containing class as well as to build a list of the available resources. Let’s look at the method currently implemented in PoolParty:
def self.inherited(subclass)
subclass = subclass.to_s.split("::")[-1] if subclass.to_s.index("::")
lowercase_class_name = subclass.to_s.underscore
# Add add resource method to the Resources module
unless PoolParty::Resources.respond_to?(lowercase_class_name.to_sym)
method =<<-EOE
def #{lowercase_class_name}(opts={}, parent=self, &blk)
add_resource(:#{lowercase_class_name}, opts, parent, &blk)
end
def get_#{lowercase_class_name}(name)
get_resource(:#{lowercase_class_name}, name) if in_a_resource_store?(:#{lowercase_class_name}, name)
end
EOE
PoolParty::Resources.module_eval method
PoolParty::Resources.add_has_and_does_not_have_methods_for(lowercase_class_name.to_sym)
available_resources << subclass
end
end
def self.add_has_and_does_not_have_methods_for(type=:file)
module_eval <<-EOE
def has_#{type}(opts={}, parent=self, &block)
#{type}(#{type == :exec ? "opts" : "{:is_present => ''}.merge(opts)"}, parent, &block)
end
def does_not_have_#{type}(opts={}, parent=self, &block)
#{type}(#{type == :exec ? "opts" : "{:is_absent => ''}.merge(opts)"}, parent, &block)
end
EOE
end
I’m going to make this quick, but feel free to stop me and ask me if something is confusing…
Whenever a class is subclassed, the parent class receives a callback hook called “inherited.” This method is called on any class that gets subclassed, for instance:
class A
def self.inherited(sublcass)
puts "inherited from #{sublcass}"
end
end
class B < A
end
# inherited from B
This way, in order to add a resource to PoolParty, all we have to do is subclass the PoolParty::Resources::Resource class.
I’m making sure that we only have to run this method once, so that both the method doesn’t get uselessly overwritten and second that it’s nicer on GC. Second, we are adding two methods at the time to the resources (note, that clouds, pools, and plugins all include the Resources module, thus all these methods are allowed within those contexts.
Finally, we add the methods onto the class, add the add_has_and_does_not_have_methods_for methods to the class (this is where the has_ and does_not_have methods get added).
One thing you’ll note from this is that every single resource created can be called 3 ways, resource_name, has_resource_name and does_not_have_resource_name. Finally, we add it to the available_resources array just so we keep track of these.
I hope that works for you and that you can find a use for self.inherited.
Update
Stay tuned for a special inside PoolParty about rspec extensions… pretty hot stuff, IMHO.
Comments
blog comments powered by Disqus