How to add an attribute to an object using the meta-object protocol? - class

I was trying to answer this question, and thought I could use the meta-object protocol to add attributes to a class. Here is a minimal example where I try to add an attribute test to the class Configuration after construction:
use v6;
class Configuration {
}
my $config = Configuration.new;
my $attr = Attribute.new(
:name('$.test'), # Trying to add a "test" attribute
:type(Str),
:has_accessor(1),
:package(Configuration)
);
$config.^add_attribute( $attr );
$config.^compose();
say "Current attributes: ", join ', ', $config.^attributes();
$attr.set_value( $config, "Hello" ); # <-- This fails with no such attribute '$.test'
say $config.test;
When I run this, I get:
Current attributes: $.test
P6opaque: no such attribute '$.test' on type Configuration in a Configuration when trying to bind a value
in block <unit> at ./p.p6 line 16

Attributes cannot be added after class composition time, which occurs at compile time when the closing } is reached when compiling the program. (This is the case for the P6opaque representation. It's not impossible that a representation could exist that allows this, but there's none specified at this time.)
Further to that, .^add_attribute is called on the meta-object, and for a class the attributes are per type, not per object; the code structure suggests that perhaps the expectation was per object. There's nothing that makes it impossible to have prototype object orientation (actually the MOP is designed so somebody could implement such an object system in Perl 6), but again there's nothing specified in Perl 6 itself that provides this.
Thus with the provided object system, such manipulation needs to be done at compile time, and before the closing }. That can be achieved as follows:
class Configuration {
BEGIN {
my $attr = Attribute.new(
:name('$!test'), # Trying to add a "test" attribute
:type(Str),
:has_accessor(1),
:package(Configuration)
);
Configuration.^add_attribute( $attr );
}
}
my $config = Configuration.new;
say "Current attributes: ", join ', ', $config.^attributes();
$config.^attributes[0].set_value( $config, "Hello" );
say $config.test;
This is one of the many places where Perl 6 is dynamic primarily by inviting the programmer to participate in compile time, rather than by making all things possible at runtime.
Finally, I'll note that there is a means to add attributes to an existing object, and on a per-object basis: by using does to mix a role in to it. That works by changing the type of the object along the way. There's some documentation on does here.

Related

How to find the Perl code referenced by this line?

I have inherited some Perl code which contains a line that is mysterious to me:
my $binary = A->current->config->settings('arg1', 'arg2')
Basically, I am not sure how to find the related code. "A" is NOT a variable in the local code so I thought this was a class hierarchy. However I checked the directory structure to see if the following path existed, but there was none:
A/current/config/settings.pm
Is A->current->config->settings guaranteed to be a nested class hierarchy, or could it be something else? For example could config actually be a property or method of a different object A->current?
Any assistance you could lend tracking this down would be greatly appreciated!
A is a class name, you should find it in A.pm. current should be a method of the class, defined under a sub current in A.pm. It returns an object whose config method is being called which returns an object again whose settings method is being called with arguments 'arg1' and 'arg2' (well, in fact, the object itself is the first argument).
In fact, any of the methods can return a class instead of an object, too.
Step through the code in the perl debugger and see where it takes you.
foo->bar is a method call, meaning that there is likely a subroutine called bar defined in the package referred to by foo (or a superclass), and gives you no information about whether there is a package bar or foo::bar.
Is A->current->config->settings guaranteed to be a nested class hierarchy
You're thinking of A::current::config::settings.
The following are method calls:
INVOCANT->name
INVOCANT->name(LIST)
That means that A->current->config->settings is a chain of method calls.
The only class named in that code is A.
could config actually be a property or method of a different object A->current?
It's the name of a method of the object or class returned by A->current.
How to find the Perl code referenced by this line?
my $binary = A->current->config->settings('arg1', 'arg2');
is short for
my $obj1 = A->current;
my $obj2 = $obj1->config;
my $binary = $obj2->settings('arg1', 'arg2');
Now that you have the objects available, you can find the class of which they are an instance using
say ref($obj) || "Not a reference";
or
use Scalar::Util qw( blessed );
say blessed($obj) // "Not an object";
As explained, you are dealing with a chain of method calls in the class named A, where at least the first one is a class method since it is invoked on the class (A) itself, not on an object.
An easy way to find that class is by using Class::Inspector
use Class::Inspector;
say "Filename: ", Class::Inspector->resolved_filename( 'A' );
which printed the full path to the class I used in my tests. Also see loaded_filename.
Another interesting way to interrogate a class is to add to it at runtime.
Create an object of A and add to it a method of your choice at runtime
my $objA = A->new();
eval q( sub A::get_info { print "$_\n" for (caller(0)) } );
if ($#) { print "Eval: $#" };
eval q( sub A::boom { croak "Stacktrace: " } );
if ($#) { print "Eval: $#" };
$objA->get_info();
$objA->boom();
These are simple examples but you can acquire practically any information from inside a method.
If A happens to not have a method called new (possible) work with methods in the given chain, starting with my $objA = A->current.
Or, you can directly add a subroutine to the package's symbol table
*{A::new_method} = sub { say "A new method" };
$any_obj_of_A->new_method();
which is now also available on all existing instances, as well as on new ones.

Bridge handler can't access stash data

I have the following code in our webapp written using Mojolicious, and it doesn't work as expected: the bridge handler doesn't get the correct stash data derived from routes (gets undef), so the rest of code fails, however, debug output of $self->stash('city') in any of route handlers is as expected.
...
# Router.
my $r = $self->routes->bridge->to('Main#common');
$r->route('/')->to('Main#index')->name('start');
$r->route('/:region/:city/category/:id')->to('Main#list_category')->name('list_category');
$r->route('/:region/:city/part/:id/:name')->to('Main#show_part')->name('show_part');
...
# Controller.
sub common
{
my $self=shift;
my $db=$self->db;
my $city=$self->stash('city');
my $region=$self->db->selectrow_hashref('select * from region where LOWER(translit)=? ORDER BY region_id LIMIT 1',undef,$city);
say "City=$city.";
if(!$region)
{
$region={};
}
$self->stash(region=>$region);
return 1;
}
...
I think it's correct behavior.
Look at this code.
Placeholder is added when the appropriate route is taken to the processing, i.e., step by step.
Really, look at you routes.
my $r = $self->routes->bridge->to('Main#common');
$r->route('/')->to('Main#index')->name('start');
$r->route('/:region/:city/category/:id')->to('Main#list_category')->name('list_category');
$r->route('/:region/:city/part/:id/:name')->to('Main#show_part')->name('show_part');
I can't understand what behavior you expect when go to route /.
Sub common will be invoked in this case. There are no value for placeholder city!
So, correct solution for your routes must look like this:
my $r = $self->routes;
$r->route('/')->to('Main#index')->name('start');
my $r_city = $r->bridge('/:region/:city/')->to('Main#common');
$r_city->route('/category/:id')->to('Main#list_category')->name('list_category');
$r_city->route('/part/:id/:name')->to('Main#show_part')->name('show_part');
By the way,
starting from Mojolicious version 6.0 bridge was deprecated to favor under. So, you need to replace bridge on under.
But, if you very-very want to have value of placeholder city in common function, you may look at this two line.
You need to write this BAD code in common sub:
sub common {
my $self = shift;
my $stack = $self->match->stack;
warn $self->dumper($stack);
...
}
Print $stack and you understand how to get value of placeholder city.

Supporting "recursive objects" in lua

I'm fairly new to lua and have the following problem with an assignment from a class:
We currently extend lua to support objects and inheritance. The Syntax for that is
Class{'MyClass',
attribute1 = String,
attribute2 = Number
}
Class{'MySubClass', MyClass,
attribute3 = Number
}
This works perfectly fine. The real problem lies within the next task: We should support "recursive types", that means a call like
Class{'MyClass', attribute = MyClass}
should result in an class with a field of the same type as the class. When this "class-constructor" is called the variable MyClass is nil, thats why the parameter table doesnt't have an entry attribute. How is it possible to access this attribute?
My first thought was using some kind of nil-table which gets returned every time the global __index is called with an unset key. This nil-table should behave like the normal nil, but can be checked for in the "class-constructor". The problem with this approach are comparisons like nil == unknown. This should return true, but as the __eq meta method of the nil-table is never called we cannot return true.
Is there another approach I'm currently just ignoring? Any hint is greatly appreciated.
Thanks in advance.
Edit:
Here the relevant part of the "testfile". The test by which the code is rated in class is another one and gets published later.
three = 3
print( three == 3 , "Should be true")
print( unknown == nil , "Should be true" )
Class{'AClass', name = String, ref = AClass}
function AClass:write()
print("AClass:write(), name of AClass:", self.name)
end
aclass = AClass:create("A. Class")
aclass:write()
Since MyClass is just a lookup in the global table (_G), you could mess with its metatable's __index to return a newly-defined MyClass object (which you would later need to fill with the details).
However, while feasible, such an implementation is
wildly unsafe, as you could end up with an undefined class (or worse, you may end up inadvertantly creating an infinite lookup loop. Trust me, I've been there)
very hard to debug, as every _G lookup for a non-existing variable will now return a newly created class object instead of nil (this problem could somewhat be reduced by requiring that class names start with an uppercase character)
If you go that route, be sure to also override __newindex.
How about providing the argument in string form?
Class{'MyClass', attribute = 'MyClass'}
Detect strings inside the implementation of Class and process them with _G[string] after creating the class
Or alternatively, use a function to delay the lookup:
Class{'MyClass', attribute = function() return MyClass end}

DBIx::Class Wrapping/overloading a column accessor

Using DBIx::Class I am trying to manipulate the data of a column whenever it is being updated or retrieved. For instance, before it goes into the database I would like to encrypt it, and whenever it is being accessed I would like to decrypt it. I am following this example in the DBIx::Class::Manual::Cookbook, however I can't seem to get it to work. I have placed the following in my User schema. For testing I am just using the name column, I know it doesn't make sense:
__PACKAGE__->add_columns("name" => { accessor => '_name' });
sub name {
my $self = shift;
# If there is an update to the column, we'll let the original accessor
# deal with it.
if(#_) {
return $self->_name('test 1');
}
# Fetch the column value.
my $name = $self->_name;
$name = 'test 2';
return $name;
}
I can't see what I'm doing any different than what the cookbook says. Can't anyone help me understand what I'm doing wrong? Thanks!
DBIx::Class has a component for that called FilterColumn.
There are various modules on CPAN using that component like DBIx::Class::EncodedColumn and PassphraseColumn.
If you tell us what you use case is we might give you more/better suggestions.

When using DBIx::Class Schema Loader, is there a way to maintain custom relationships and methods in separate files?

Currently we use DBIx::Class::Schema::Loader to generate and regenerate (when our db schema changes) a set of Result classes.
We add additional relationships and methods to the bottom of these classes and this is causing merge hell when people regenerate or change the schema.
We would like to maintain our custom changes in a separate set of files that sit in parallel with the auto-generated ones.
Is there a simple, clean, recommended way of doing this?
I ran into the same problem. You can just create another class that inherits from the generated classes. However, you need to pull over the table reference, and the relationships into the class you are editing, but you can leave the column definitions and what not in the generated class. I basically wrote a helper for the loader that generates the classes into an "Immutable" namespace, and creates a child for each of them in a "Mutable" namespace along with the table name reference and the relationships from the generated model. It seems to work reasonably well, and I no longer have to worry about developers editing the generated section of the class. I should probably write up the whole thing in a blog post one of these days.
I solved it by Moosifying the schemas and then creating a set of Moose::Roles that I apply to the Schema classes just after schema->connection();
It goes a little like this:
my $schema = My::Schema->connection();
foreach my $source ($schema->sources) {
my $domain_pkg = "My::Domain::$source";
eval "require $domain_pkg";
# ignore failures due to file-not-found
if ($# && $# =~/^Can't locate.*INC/) {
# but barf if class doesnt compile
} elsif ($#) {
confess "Failed to load $domain_pkg for $pkg!!: - $#";
# re-register domain class with the resultsource
# and apply the role
} else {
my $schema_pkg = "${pkg}::$source";
$c->register_class($source, $schema_pkg);
use Moose::Util;
# check schema is moosyfied
if ( $schema_pkg->can('meta') ) {
my $meta = $schema_pkg->meta;
eval {
Moose::Util::apply_all_roles($meta, $domain_pkg);
};
if ($#) {
confess "Failed to add $domain_pkg role to $schema_pkg: $#\n";
} else {
l4p->info("Found and applied Domain role: '$domain_pkg' for schema: '$schema_pkg'");
}
} else {
warn "Cant call meta on $schema_pkg. ";
}
}
}
Nearby..
use MooseX::Declare
role My::Domain::Person {
# modify schema
My::Schema::Person->inflate_column( ..);
My::Schema::Person->belongs_to(..);
My::Schema::Person->set_primary_key(..);
# add some method modifiers to check/modify construction
around new (ClassName $class : $params) {
munge params..
$self->$orig($params);
}
# post insert hook
after insert () {
do_something..
}
# domain methods
sub fullname {
$self->firstname.' '.$self->surname;
}
}
While this isn't technically an answer to the question, it is a solution to the merge hell problem that spawned it. When calling dbicdump or make_schema_at, you could set the omit_version and omit_timestamp flags, which will generate a signature like below:
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:CKsL4EO4b/JE3QXBSC4EXg
When re-dumping, this signature shouldn't change unless the actual tables do, so any version control won't see unreasonable conflicts.