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.