Models
Creating Your Own Components
If you’ve looked at the section on SubBlocks
and at the section on Components,
you’ll probably notice many similarities. It may seem that SubBlocks are just a more sophisticated and feature
rich version of Components.
The Origen::Componentable module (henceforth referred to as just Componentable) is a
mixin. The name Componentable is
a protologism taking after Ruby’s own Enumerable mixin. By mixing in Componentable with
your classes, you will automatically be given methods to add, remove, move, copy, and query objects you’ve added. Now,
you may think… “well, that’s easy enough to do on my”, and that’s very true. But, Componentable will
handle all that boiler plate code for you while providing a common interface and having an easily managed/updated
mixin so that bug fixes and feature enhancements will be felt across all your Componentable classes
without any intervention from you (other than updating the Origen version, that is).
Furthermore, Componentable can handle initiailizing Componentable's parent classes. This
means that in addition to your class receiving add, remove, query, etc. methods internally, any parent
that uses your class will also receive add, remove, query, etc. methods, again, without any intervention.
If this is of interest to you, sections below will discuss how to mixin the Componentable module, how to
handle instantiating and initializing your classes, and what the API provides.
Mixing in Componentable is easy and does not require that any particular methods be defined. You
can mix it in by just putting the following into your class:
include Origen::Componentable
Componentable actually contains two APIs: The first is the API provided to the includer class,
that is, the class that includes Componentable. This includer class is then added to a
parent class.
In other words, the parent class is the one that users of you new Componentable class will
interact with. Whereas the includer class acts as the interface between the parent class and
the structures that actually hold and maintain the items.
If this is still confusing, it should make more sense as you move through this guide and for now, just know that the
class that contains include Origen::Componentable is the includer class and the class that
users the componentable class is the parent class.
There’s quite a few sections to this guide. The marks below should help you jump around appropriately.
- The Componentable Class
- Booting the Includer
- Includer Class API
- Includer Class Organization
- Naming the Includer Class
- Conclusion
- The Parent Class
- Booting the Parent
- The Parent API
- Controlling Accessors on the Parent
- Example Parent API
- Generic Parent API
- Componentable Class
- Componentable Class
- Componentable Class
All that Componentable requires is an instance variable called _componentable_container. This
is a generic name that the API below
will interact with.
Depending on how you setup your class, you still may not need to do anything. If your componentable class includes
Origen::Model, then the boot process will take place automatically. Componentable implements
an origen_model_init callback handler for you
(see here).
This callback will add and initialize this object during the includer class's initialization.
So, the following will take care of everything for you:
include Origen::Model
include Origen::Componentable
If you are not using include Origen::Model, you’ll need to take some action yourself.
Componentable comes with a method to assist with this:
init_includer_class (includer being the class that includes Componentable).
Calling this method in your class’s initialize function will accomplish the same thing as including
Origen::Model. The usage is:
# Initialze the _componentable_container object
# Note that this is in the Origen::Componentable module's class methods, not in the mixin.
# Initialize internal to the class
class Includer
include Origen::Componentable
def initialize(options={})
Origen::Componentable.init_includer_class(self)
end
end
# Initialize externally an already existing object
class Includer
include Origen::Componentable
end
i = Includer.new
Origen::Componentable.init_includer_class(i)
After booting, the includer class is ready!
This is the API that includer classes will get for free. However, any of these
can be overridden or extended to further customize the Compentable object.
The basic API includes these methods:
Adding new objects
add(name, options={}, &block)
#=> adds a new object, :name
add(name, class_name: ClassToAdd
#=> adds a new object of class ClassToAdd
add(name) do |n|
n.class_name ClassToAdd
end
#=> (also) adds a new object of class ClassToAdd using block syntax
Querying objects
list
#=> list the available names in the componentable container
has?(name)
#=> returns true if and object with :name has been added
[](name)
#=> returns the object at :name, or nil if :name has not been added
Moving and copying objects
copy(to_copy, to_location, options = {})
#=> # copies an object from to_copy to to_location, cloning the object
move(to_move, new_name, option={})
#=> Moves an object from to_move to new_name
Iterating through objects
each(&block)
#=> runs the generic :each method from enumerable across the underlying componentable container hash
select(&block)
#=> runs the generic :select method from enumerable across the underlying componentable container hash
instances_of(class_name)
#=> returns an array of all the names of class :class_name
Removing objects
# aliased as :remove
delete(to_delete)
#=> deletes the object at :to_delete, returning the object
# aliased as :clear and :remove_all
delete_all
#=> deletes everything in the componentable container
All of these will operate on the componentable_container object and all are provided to you. These
methods will also be called by the parent class to perform these operations, but more on that later. You
should just know that undefining these methods or changing around their arguments will change the way that the
parent class will need to interact with the includer class.
In order to use this to the full power provided, the namespacing must be set up rather specifically. You can always boot
the classes manually, so don’t let this put you off from creating your own Componentable Classes but the
example namespacing below will show you the recommended way of organizing the modules and classes
such that the parent class can bring in you new Componentable class with a single line.
As will be covered in later when booting the parent class,
the goal is to bring in your Componentable class as a single line. If you decided to name your
Componentable class MyTest for example, then what we’re looking for in the
parent class is simply:
class Parent
include Origen::Model
include MyTest
end
Well, what’s the problem with this right now? MyTest isn’t a module. Its a class. We can’t
include a class, Ruby won’t let us. But we can do is namespace our MyTest class in a module
also named MyTest, in this case. When this module is included, Componentable will signal
to Origen that this module MyTest contains Componentable classes that must be booted. So,
a simple implementation may look like:
module MyTest
class MyTest
include Origen::Model
include Origen::Componentable
end
end
Note that the name of the module actually isn’t important. As long as the module contains some
Componentable classes everything will go as expected. You aren’t even limited to a single class per module.
If you have a few Componentable classes in a module, they will each be booted.
module MyTests
class MyBasicTest
include Origen::Model
include Origen::Componentable
end
class MyIntermediateTest
include Origen::Model
include Origen::Componentable
end
class MyAdvancedTest
include Origen::Model
include Origen::Componentable
end
end
An in-depth explanation of how this occurs is out of scope right now, but you can see the section on advanced booting if you wish to know more and get a developers perspective.
Define the API based on the name of the includer class? Yep! When trying to add the parent API, two names will be used:
the singleton_name and the plural_name. The process for this is:
- Use Ruby’s/Rail’s underscore method as the
singleton_name - Apply a few general rules that covers most cases for pluralizing. This will be the
plural_name
In general, this will just be adding an s to the singleton_name, but it can handle a few
other cases, such as:
- ‘is’ -> ‘es’ (as in ‘analysis’ -> ‘analyses’)
- ’s/h/ch/sh/x/z’ -> ‘es’ (as in ‘bus’ -> ‘buses’, or ‘box’ -> ‘boxes’)
- ‘on’ -> ‘a’ (as in ‘criterion’ -> ‘criteria’)
- all other cases will just ‘s’ appended (as in ‘component’ -> ‘components’)
You can run this method on your class yourself in interactive mode. This returns a Hash with keys
singleton_name and plural_name. For example:
Origen::Componentable.componentable_names(Origen::Component)
{
singleton_name: :component
plural_name: :components
}
Origen::Componentable.componentable_names(MyTest)
{
singleton_name: :my_test
plural_name: :my_tests
}
We here at Origen are engineers, not writers, and not interested in figuring out all rules of how to make
everything plural (I can tell you, the author of this particular section is a native english speaker, and still got
half rules wrong before looking them up). So, if you don’t like the name Compomentable gave you, no problem!
You can define it yourself using two class constants (not instance variables). The following
examples will show this:
class MyTest
COMPONENTABLE_SINGLETON_NAME = 'just_test'
end
# This will take the 'just_test' singleton_name, and pluralize it as using that name
{
singleton_name: :just_test
plural_name: :just_tests
}
class MyTest
COMPONENTABLE_PLURAL_NAME = 'just_tests'
end
# This will evaluate the singleton name as normal, but use the given plural name
{
singleton_name: :my_test
plural_name: :just_tests
}
class MyTest
COMPONENTABLE_SINGLETON_NAME = 'test'
COMPONENTABLE_PLURAL_NAME = 'tests'
end
# This will use both the provided singletoneand plural name.
{
singleton_name: :test
plural_name: :tests
}
Ruby itself has some requirements though. Due to how these names will be used, they can’t start with a capital letter.
So, even if you define a COMPONENTABLE_SINGLETON_NAME and/or a COMPONENTABLE_PLURAL_NAME name,
they are getting downcased either way or Ruby won’t let us run anything.
Note that even this breaks down for words like deer, as Componentable requires that the
singleton name and plural name be different (otherwise methods will step on each other), and will complain if this
is not met. So, apologies, but you’ll have to keep the plural of your Deer class as Deers,
which is technically correct actually, but not really important so…
moving on.
This covers everything the includer class gets for free. Next, we’ll discuss the parent class.
For information on customizing the includer class you can jump
here,
but this may not make as much sense without first getting the parent’s perspective.
The parent API is really what is interesting. When you use the generic Origen components, say, for example, on your
$dut, you use methods like $dut.add_component, $dut.components_of_class,
$dut.has_component?, etc. But where do these methods come from? You would think that these are part
of Origen, that somewhere deep in bowels of the code base are a bunch generic methods to operate on some
Component class somewhere. But, that’s not true.
Those methods are added by booting the parent class of a Componentable
includer class
Since we can’t do much without booting the parent class, we’ll cover that now. Booting the parent is
more involved than booting the includer. That said, if the includer class is set up a certain way,
as discussed here then
the boot process for the parent can be automated as well. Booting the parent class includer has two steps:
- Instantiate the includer class as an instance variable on the parent class.
- Define the API on the parent class based on the name of the includer class.
The easiest way is just to let Origen to do it for you. Like the includer class,
including Origen::Model will let us hook into the intializer to take care of booting the parent for you.
includer class is also either an includer of
code>Origen::Model</code> OR intializes itself. If not, you'll end up with a fully built parent-API and its very
own instance variable pointing to an includer class... that has no comopnentable container.
As you can imagine, all of the attempt to use the includer will result is errors. You can boot it yourself there
in the parent as discussed here. This can
also be used to boot includer classes that are not setup as described
here.
If we let Origen do it, and if our includer classes are setup as shown in the
includer organization section,
we can simply do this by include the module, including Origen::Model and going on our way.
class Parent
include Origen::Model
include MyTest
end
parent = Parent.new
parent.my_test #=> Instance of MyTest Componentable class.
parent.list_my_tests #=> list the current added my_tests, in this case, []
But, say we want to boot it directly. That is, not rely on Origen. We can do that using the
method Origen::Componentable.init_parent_class (class method within the Origen::Componentable
namespace). We can give this both the instance of the parent class and the includer class
and it will handle booting for you. For example:
# Internal to the parent class
class Parent
#include MyTest #=> nevermind, don't need this right now.
def intiailize
Origen::Componentable.init_parent_class(self, MyTest)
end
end
# External to the parent class
class Parent
#include MyTest #=> nevermind.
def intiailize
#Origen::Componentable.init_parent_class(self, MyTest) #=> Nevermind this too.
end
end
p = Parent.new
Origen::Componentable.init_parent_class(p, MyTest)
And… we’re done! Now we’ve got the parent booted up and we’re ready to start using it.
Now, the API for the parent is bit trickier… as its not static. What exactly the parent API is will be dependent on what you’ve named your class.
Continuing on with our MyTest includer class example, we can use the naming convention
here to know that:
- The singleton name is:
my_test - The plural name is:
my_tests
After booting, we’ll be able to use our includer class on the parent:
p.my_test #=> MyTest object
p.my_tests #=> {}
p.add_my_tests(:test1)
p.list_my_tests #=> ["test1"]
p.add_my_tests(:test2) do |t|
t.class_name TestClass
end
p.list_my_tests #=> ["test1", "test2"]
p.my_tests["test1].do_something
#=> run method :do_something with object at name "test1"
Since this API is not static, its more difficult to document. The generic API is actually just placeholders that can be found here.
This API will show which Componentable method is called. So, calling the method from the parent
class boils down to calling an includer class method and all of relevent and defined options will be
passed down to it. You can review the full includer API here.
As with compoments, we added COMPONENTABLE_ADDS_ACCESSORS = true
constant in the MyTest class. So, we can expect:
p.test1 #=> Object stored in MyTest at name :test1
p.test2 #=> Object stored in MyTest at name :test2
But, say we already had method :test1 on the parent? The result would be a
Origen::Componentable::NameInUseError. And if we had multiple test classes, and all
had a :test1 and all decided to add COMPONENTABLE_ADDS_ACCESSORS = true, what can
our parent do?
For a new example, say we have two includer classes: MyClockingTest and MyPowerTest, and
both decided that COMPONENTABLE_ADDS_ACCESSORS = true. Also, our specification says that both
need to have a test named :test1. We have a few ways we can deal with this.
First, we can just disable all accessors on the parent unconditionally (including components).
This takes away the ability for us to use parent.test1, but now we can
have parent.my_clocking_tests[:test1] and parent.my_power_tests[:test1] coexisting. We
can do this by setting an attr_reader or defining a method :disable_componentable_accessors
and having it return true. Everytime an accessor is added, it will see that on the parent and decide
against adding said accessor.
But, we can also dictate which includer classes shouldn’t have accessors and which should.
Maybe we want to disable only the accessors on MyClockingTest and MyPowerTest, but leave them
for Compoonents.
If you define the method :disable_componentable_accessors to instead take an argument,
Componentable will give it the includer class requesting to add an accessor, as a class name.
We can write a method like so to do this:
def disable_componentable_accessors includer_class
if includer_class == MyClockingTest || includer_class == MyPowerTest
true
else
false
end
end
You can build this method however you see fit on the parent to only get the accessors you want from the various includers.
Using our MyTest includer class again, imported by our Parent parent class, the full API we have
available on the parent is below. You can skip to here
to view the API in generic terms.
We know from before that the singleton_name is :my_test and that plural_name is
:my_tests. With these in mind, the API on the parent class will resolve to:
Basics
# Retrieving an instance of the MyTest object
parent.my_test
#=> instance of MyTest
# Retrieving MyTest's componentable container
parent.my_test._componentable_container
parent.my_tests
#=> Hash with indifferent access
# Retrieving MyTest instance variable directly on the parent
instance_variable_get("@_my_test".to_sym) #=> instance variable on the parent class
Adding
# Adding Objects
# These methods correspond to a my_test.add call.
parent.my_test(name, options={}, &block)
parent.my_tests(name, options={}, &block)
parent.add_my_test(name, options={}, &block)
# add_my_test is aliased to:
#=> add_my_tests
Querying
# Listing Names of Objects
# These methods correspond to a my_test.list call
parent.list_my_tests
# Querying Objects
# These methods correspond to a my_test.instances_of call
parent.my_tests_of_class(class_name)
# my_tests_of_class is aliased to:
#=> my_tests_instances_of
#=> my_tests_of_type
Iterating
# Emumerating
# These methods correspond to a my_test.each call
parent.my_tests(&block)
parent.each_my_test(&block)
# each_my_test is aliased to:
#=> all_my_tests
# Selecting
# These methods correspond to a my_test.select call
parent.select_my_tests(&block)
# select_my_tests is aliased to:
#=> select_my_test
Copying and Moving
# Copying
# These methods correspond to a my_test.copy call
parent.copy_my_test(to_copy, to_location, options={})
# copy_my_test is aliased to:
#=> copy_my_tests
# Moving
# These methods correspond to a my_test.move call
parent.move_my_test(to_move, to_location, options={})
# move_my_test is aliased to:
#=> move_my_tests
Deleting
# Deleting Single Objects
# These methods correspond to a my_test.delete call
parent.delete_my_test(name)
# delete_my_test is aliased to:
#=> delete_my_tests
#=> remove_my_test
#=> remove_my_tests
# Deleting All Objects
# These methods correspond to a my_test.delete_all call
parent.delete_all_my_tests
# delete_all_my_tests is aliased to:
#=> clear_my_tests
#=> remove_all_my_tests
Also recall, that these methods link directly back to just calling the corresponding method on the includer class,
so the full method documentation (parameters, options, return values, exception raised, etc.) can be found back at the
includer class’ API.
In more generic terms, adding an includer to a parent yields the following API on the parent class:
Recall here that you can use the methods
_singleton_name and _plural_name on an instance of an includer class to
query those names. You can also use the class method Origen::Componentable.componentable_names(includer)
to get a hash containing the keys :singleton and :plural, either with an instance of
the includer class or with the class directly.
Once again, these methods link directly back to just calling the corresponding method on the includer class,
so the full method documentation can be found back at the includer class’ API.
Basics
# Retrieving an instance of the includer class object
parent.<singleton_name> #=> instance of the includer class
# Retrieving an instance of the includer class's componentable container
parent.<singleton_name>._componentable_container
parent.<plural_name>
# Retrieving the includer instance variable directly
instance_variable_get("@_<singleton_name>".to_sym) #=> instance variable on the parent class
Adding
# Adding Objects
# These methods correspond to a <singleton_name>.add call.
parent.<singleton_name>(name, options={}, &block)
parent.<plural_name>(name, options={}, &block)
parent.add_<singleton_name>(name, options={}, &block)
# add_<singleton_name> is aliased to:
#=> add_<includer_plural_name>
Querying
# Listing Names of Objects
# These methods correspond to a <singleton_name>.list call
parent.list_<plural_name>
# Querying Objects
# These methods correspond to a <singleton_name>.instances_of call
parent.<plural_name>_of_class(class_name)
# <plural_name>_of_class is aliased to:
#=> <plural_name>_instances_of
#=> <plural_name>_of_type
Iterating
# Emumerating
# These methods correspond to a <singleton_name>.each call
parent.<plural_name>(&block)
parent.each_<singleton_name>(&block)
# each_<singleton_name> is aliased to:
#=> all_<plural_name>
# Selecting
# These methods correspond to a <singleton_name>.select call
parent.select_<plural_name>(&block)
# select_<plural_name> is aliased to:
#=> select_<singleton_name>
Copying and Moving
# Copying
# These methods correspond to a <singleton_name>.copy call
parent.copy_<singleton_name>(to_copy, to_location, options={})
# copy_<singleton_name> is aliased to:
#=> copy_<plural_name>
# Moving
# These methods correspond to a <singleton_name>.move call
parent.move_<singleton_name>(to_move, to_location, options={})
# move_<singleton_name> is aliased to:
#=> move_<plural_name>
Deleting
# Deleting Single Objects
# These methods correspond to a <singleton_name>.delete call
parent.delete_<singleton_name>(name)
# delete_<singleton_name> is aliased to:
#=> delete_<plural_name>
#=> remove_<singleton_name>
#=> remove_<plural_name>
# Deleting All Objects
# These methods correspond to a <singleton_name>.delete_all call
parent.delete_all_<plural_name>
# delete_all_<plural_name> is aliased to:
#=> clear_<plural_name>
#=> remove_all_<plural_name>
This section will go over some ways to further customize the Componentable class. Recall that any of the
Componentable class’s API can be
overridden by the includer class.
Most likely, you will want to override the add method to cover how to add your custom components. The
standard add method will simply instantiate the given class (or a default class if none is given), check
that the name doesn’t already exist, add the component, then provide an accessor if set to do so. Not much. It does a bit
more if given an instances option, but that is handled as well.
However, add is actually split into a few methods that you can use if you override add.
The add method itself calls two methods: _split_by_instances and _add.
_split_by_instances is responsible for cutting up the options given into chunks for each instance. You
can see the component’s page on multiple instances for a refresher
but this will the instances option handling for your class in check with other Componentable
classes. Once split up, each individual instance and its options are given to the _add method.
_add does three things: it puts in some default options, calls the _instantiate_class method
and calls the </code>_push_accessor</code> method. Most likely, this is what you’ll want to override. From here, you
can build in any defaults/settings that you want and throw errors if things are missing or incorrect. Then you can
finish up by calling the two aforementioned methods. For example, the code for the default _add is just:
def _add(name, options={}, &block)
# Add the name and parent to the options if they aren't already given
# If the parent isn't available on the includer class, it will remain nil.
options = {
name: name,
parent: parent
}.merge(options)
if block_given?
collector = Origen::Utility::Collector.new
yield collector
options.merge!(collector.store)
end
# Instantiate the class. This will place the object in the @_componentable_container at the indicated name
_instantiate_class(name, options)
# Create an accessor for the new item, if indicated to do so.
_push_accessor(name, options)
@_componentable_container[name]
end
So feel free to override that, adding some custom behavior for your class.
_add should be overridden, if any.
This section is just as more for Origen developers than it is for users. Booting these classes took some behind the scenes work that most users will never see. But, there’s some assumptions that are made (in addition to the ones here) that can adversely affect booting.
Here, we’ll briefly touch on the behind-the-scenes actions taken to boot Componentable.
First, when you setup the module as:
module MyTest
class MyTest
include Origen::Model
include Origen::Componentable
end
end
The line include Origen::Componentable will trigger Componentable's included? method to kick
off. This adds the callback origen_model_init
to the MyTest module (not the class). This
allows the module, in this case MyTest, to boot the MyTest class upon being
included in a parent class. So, with this, we have an additional assumption that module MyTest does not
have an origen_model_init callback already.
In most cases, the module housing the Componentable classes will do just that. They won’t have other
stuff in them. But in them event that it does have its own origen_model_init,
Componentable can still function with some workaround. All that needs to happen is a manual call to
Origen::Componentable.init_parent_class(klass, self), where klass is the
Componentable class to boot. In this case, MyTest. For example:
module MyTest
def origen_model_init(klass, options={})
# ...
# Add booting the Componentable classes
Origen::Componentable.init_parent_class(klass, self)
end
class MyTest
include Origen::Model
include Origen::Componentable
end
end
This will be called for every class containing Origen::Componentable in the model.
Componentable can be used with anonymous classes, but since anonymous classes don’t have a name, obvisously, one
will need to be provided. Recall from the section on naming
that you can override the class name by defining the class constant COMPONENTABLE_SINGLETON_NAME. This
will ‘name’ the anonymous class so that Componentable can generate the parent API. Failing to provide
this to an anonymous class results in a Origen::Componentable::Error exception.