I have a class (Foo), Moose based, that has 4 properties, lets say:
SF1...SF4
each of type HashRef[Any].
Currently all have default values. Later, we are going to get these values from a MySQL table.
My approach was, to have the Foo class consume roles depending on where the data comes from, I can store the SF1...SF4 in a role called Foo::DB which it will provide the SF1...SF3 with the default values from the database.
And also to have a role, Foo::Local, which will have the default values hard-coded, so later, when we will use the DB, I will only need to change the 'with....'.
Am I going in the right direction, or I should do it differently?
It's not clear why you need to populate the data from a role. I think you can just use initializer subs. Make your attributes lazy, and then define init_attribute subs. The first time the value of the attribute is needed, if it is not already set, then initializer sub will be called to provide the value. When you plug in the database you can simply teach your initializers how to query the database for the values.
package Foo;
has SF1 => ( is => 'rw', lazy => 1, isa => 'HashRef[Any]' );
sub init_SF1 {
{ hi => 'how are you' }
};
Alternatively, if you want to be able to go back and forth (e.g., for testing), then yes, you can bundle your initializers into the roles and apply the role depending on the situation. Or you can just supply the values inline in your tests. For instance
use Test::More;
use Foo;
my $foo = Foo->new(
SF1 => {
row1 => 'fake test data',
row2 => 'also fake'
},
SF2 => {},
); # now init_SF[12] will not be called
If you tell me why you're doing this, then I can give you a better answer.
Let me see if I understand you correctly.
You have four attributes that should be assigned a default value.
You have defined that default value in the MySQL database schema. What you would like to happen is that anytime you create a Foo instance, the default values will be populated from the defaults you have defined in the MySQL schema.
If I am correct in my understanding of what you are trying to do, then my advice is: Don't do it that way (unless this absolutely a requirement to your project). Define the default values of your attributes using Moose's default or builder properties.
has 'bar' => (
default => 'fubar',
);
If you were to lookup the default values that have been set in the database schema instead of defining them in your class you will create more work for yourself, add unnecessary complexity to your program, and will be adding expensive database calls that could be avoided. You would need to parse the database schema and determine what the defaults should be for the given attribute. You would either need to do this every time you created a new object (expensive) or create a cache of the default values. Sure you could create a Moose extension that implements some magic and does this for you transparently. Seems like a lot of work for a not-so-appealing solution. I would just use Moose's 'default' attribute property unless you have a really good reason not to.
Related
I have a Moose::Role that contains a network client as an attribute:
package Widget;
use Moose::Role;
has 'network_thingy' => (
isa => Maybe[ThingyClient],
);
And of course, I have a couple concrete Moose classes which use this role:
package BlueWidget;
use Moose;
with 'Widget';
Now it comes to functional testing of the Widgets. We have the ability to create ThingyServer objects, and it would be much faster and overall excellent to directly use ThingyServer objects instead of spinning up a daemon and having a ThingyClient connect to it over the network. Since ThingyClient & ThingyServer conveniently have the exact same methods, this should be easily possible. But of course, Moose is demanding that I use a ThingyClient when the test eventually constructs a BlueWidget.
I did some research, and came across the Moose::Meta documentation. Seemed perfect! So here's the test code:
my $metarole = Moose::Meta::Role->initialize('Widget');
// first remove the old attribute
$metarole->remove_attribute('network_thingy');
I was going to add a new attribute, but I thought I'd check on the state of the role & class first. Now if I dump out the $metarole, it looks great. There's no network_thingy attribute anymore. But if I construct a BlueWidget class, or just peak inside the metaclass...
$metaclass = Moose::Meta::Class->initialize('BlueWidget');
diag Dumper ($metaclass);
... sure enough network_thingy is still there. This is not at all what I expected. How can I modify/remove/replace an attribute of the Widget role at runtime?
When a class consumes a role, attributes are copied from the role to the class. If you then change the attribute in the role, the copy in the class is unaffected.
So you would need to loop through the classes that have consumed the role, and change the attribute in each class. There's a consumers method in Moose::Meta::Role that could help you get a list of classes that have consumed the role, however it only covers classes that have directly consumed the role, and not, say, subclasses of those.
If the classes have been made immutable (__PACKAGE__->meta->make_immutable), you'll need to make them mutable again before you make modify the attribute.
Overall, it's probably a better idea to just alter the role module (i.e. edit the file); not attempt to tweak the attribute at run time. Maybe set isa to a duck_type type constraint?
I have a small (soon to be large) set of Moose roles that interact with a database. They have methods like create_foo_record, update_foo_record, delete_foo_record, etc. (They aren't simple wrapper for database access, as they do things like translate and normalize data, log actions, etc. In some cases, they may create and update related records too.)
This code is pretty generic, so rather than have two dozen modules of cut-and-paste code, I would like a base module that implements these methods, using configuration parameters telling them what tables, columns, normalization functions, etc.
My instinct is to write something "higher-order" that generates these functions, but I think it's more appropriate to do that the Moose way. (I am a Moose novice, however...)
The naive way would be to have the base class be a role, and it's methods take configuration arguments, but this seems messy.
Ideally, I'd like the base class to have private configuration that the roles that use it set up, something like
__PACKAGE__->config( foo => 'bar' );
but I am unsure how to do this using Moose. My attempts have gotten errors complaining about extending a class into a role. Or the config parameters turns out not to be private, and interferes with other roles or is being interfered with by the corresponding config in Catalyst controllers that use the roles.
If it's possible to make use of 'Catalyst::Component' in the base class but somehow extend that to roles, that would be ideal. But I don't know how to do that, or even if it is possible.
Thanks.
Leaving aside that this sounds an awful lot like DBIx::Class::SchemaLoader, if I understand you correctly you want...
A thing to store system configuration information.
It has defaults.
Those defaults can be overridden on a per class basis.
One way to do it is to create a role and take advantage of builder methods. The role defines the defaults via the builder methods, and each class can override.
use v5.10;
{
package MyConfig;
use Moose::Role;
has user => (
is => 'ro',
lazy => 1,
builder => '_build_user',
);
sub _build_user {
return "default_user";
}
}
{
package MyApp;
use Moose;
with 'MyConfig';
sub _build_user {
return "some_user";
}
}
{
package DefaultApp;
use Moose;
with 'MyConfig';
}
say MyApp->new->user; # some_user
say DefaultApp->new->user; # default_user
Another is to take advantage of attribute inheritance.
use v5.10;
{
package MyConfig;
use Moose::Role;
has user =>
is => 'ro',
lazy => 1,
default => "default_user",
;
}
{
package MyApp;
use Moose;
with 'MyConfig';
has '+user' =>
default => 'some_user'
;
}
{
package DefaultApp;
use Moose;
with 'MyConfig';
}
say MyApp->new->user; # some_user
say DefaultApp->new->user; # default_user
They're basically the same thing. The former is done in a more traditional OO style and is more mechanistic. The latter is done using a bit of Moose magic and is more declarative and compact. Each has minor advantages and disadvantages, use which ever is comfortable to you.
I think what I need are parameterized roles http://metacpan.org/pod/MooseX::Role::Parameterized
I have started to use DBIx::Class and I really like it for the most part but what is really starting to annoy me is the column alias does not seem to work fully.
Eg. Suppose I have this table definition:
#TestClass.pm
use strict;
use warnings;
package Database::Schema::Result::TestClass;
use base qw/DBIx::Class::Core/;
__PACKAGE__->table("TEST_TABLE");
__PACKAGE__->add_column("ID")
__PACKAGE__->add_columns(NAME => {accessor => "name"},
VALUE => {accessor => "value"}
);
And then I try to create a new row as follows:
$schema->resultset("TestClass")->create(name => "test", value => "value");
The above will say:
DBIx::Class::ResultSet::create(): No such column name on Database::Schema::Result::TestClass
However the following works fine:
$schema->resultset("TestClass")->create(NAME => "test", VALUE => "value");
If later on I have TestClass object and try to access its columns as such:
$object->NAME;
I get Can't locate object method "NAME" via package "Database::Schema::Result::TestClass"
but this is ok:
$object->name
I would expect to be able to create the object using the accessor I provided the column and for creation of the object and accessing the columns to be consistent but this does not seem to be the case. Can anyone explain why this is?
I think the answer to your dilemma lies in part 3 of the DBIx::Class Tutorial. Also, the manual page for the add_columns method of DBIx::Class::ResultSource class states that: Use this (the accessor property) to set the name of the accessor method for this column. If unset, the name of the column will be used.
Basically what you did was to define a column NAME and create an accessor for it name
I have a Moose::Role that I would like to call some extra subs on the class when that role is applied to the class.
Is there an easy way to modify what happens when the role is applied, without having to dig too much into Moose::Meta::Role type coding? Ideally, I'd just like to after 'apply' => ... to add the extra stuff.
Edit:
I'm specifically using this with a DBIx::Class::Core result definition to create something like a component that also modifies the constructor. I would just write it as a component if I could get at BUILDARGS and BUILD subs for the result, but I can't seem to do. So, instead of doing load_component, I doing with 'role', but some of the effects of the component are to add belongs_to relationships to the class. Hence, I was thinking the best way to do that is during application of the role to the class.
In a briefly-lived comment I referred you to this question, which discusses how to access the metaclass of the class the role is being applied to (e.g. so you can build onto the class conditionally). However, that's a really stinky use of MooseX::Role::Parameterized providing you that information, and it also won't work if the role is being applied to another role, not to a class.
As an alternative, you could write a sugar function which receives the meta information, and build onto the class in that way:
sub foo
{
my ($meta, %options) = #_;
# based on what is present in %options, add additional attributes...
$meta->add_attribute(...);
}
See Moose::Cookbook::Extending::Recipe4 for an example of writing sugar functions.
You could use a parameterized role. There is an example on how to access the consuming class in the tutorial. That being said, I would advise you to join the Moose and DBIx-Class IRC channels or mailing lists to look for best-practices in this regard.
What I found that works, is compact, and seems in keeping with intent in the docs is to use a trait to modify the meta role used by my particular role:
package DBIx::Class::Meta::Role::MyRole;
use Moose;
BEGIN { extends 'Moose::Meta::Role'; }
after 'apply' => sub {
## ..my mods to add extra relationships to DBIx::Class::Core result
};
no Moose;
package DBIx::Class::MyRole;
use Moose::Role -metaclass => 'DBIx::Class::Meta::Role::MyRole';
I want to create a generic class, whose builder would not return an instance of this generic class, but an instance of a dedicated child class.
As Moose does automatic object building, I do not get to understand if this something possible, and how to create a Moose class with Moose syntax and having this behaviour.
e.g.:
The user asks: $file = Repository->new(uri=>'sftp://blabla') .... and is returned an `Repository::_Sftp`` instance
User would use $file as if it is a Repository instance, without the need to know the real subclass (polymorphism)
Note:
As requested, maybe i should have been more clear about what i was trying to achieve:
The purpose of my class is to be able to add new Repository schemes (eg over sftp), by simply creating an "hidden" Repository::_Stfp class, and adding a case in the Repository constructor to factory the correct specialized object depending of url. Repository would be like a virtual base class, providing an interface that specialized objects would implement.
All of this is for adding new repository schemes without having the rest of the program to be modified: it would unknowingly deal with the specialized instance as if it is a Repository instance.
new builds the builder. You want some other method to actually return the built object.
Here's an example:
class RepositoryBuilder {
has 'allow_network_repositories' => (
is => 'ro',
isa => 'Bool',
required => 1,
);
method build_repository(Uri $url) {
confess 'network access is not allowed'
if $url->is_network_url && !$self->allow_network_repositories;
my $class = $self->determine_class_for($url); # Repository::Whatever
return $class->new( url => $url );
}
}
role Repository { <whatever }
class Repository::File with Repository {}
class Repository::HTTP with Repository {}
Here, the builder and the built object are distinct. The builder is a
real object, complete with parameters, that can be customized to build
objects as the situation demands. Then, the "built" objects are
merely return values of a method. This allows you to build other
builders depending on the situation. (A problem with builder
functions is that they are very inflexible -- it's hard to teach them
a new special case. This problem still exists with a builder object,
but at least your application can create a subclass, instantiate it,
and pass this object to anything that needs to create objects. But
dependency injection is a better approach in this case.)
Also, there is no need for the repositories you build to inherit from
anything, they just need a tag indicating that they are repositories.
And that's what our Repository role does. (You will want to add the
API code here, and any methods that should be reused. But be careful
about forcing reuse -- are you sure everything tagged with the
Repository role will want that code? If not, just put the code in
another role and apply that one to the classes that require that
functionality.)
Here's how we use the builder we created. If, say, you don't want to
touch the network:
my $b = RepositoryBuilder->new( allow_network_repositories => 0 );
$b->build_repository( 'http://google.com/' ); # error
$b->build_repository( 'file:///home/whatever' ); # returns a Repository::Foo
But if you do:
my $b = RepositoryBuilder->new( allow_network_repositories => 1 );
$b->build_repository( 'http://google.com/' ); # Repository::HTTP
Now that you have a builder that builds the objects the way you like,
you just need to use these objects in other code. So the last piece
in the puzzle is referring to "any" type of Repository object in other
code. That's simple, you use does instead of isa:
class SomethingThatHasARepository {
has 'repository' => (
is => 'ro',
does => 'Repository',
required => 1,
);
}
And you're done.
No (not directly). In general in Moose, calling CLASS->new where CLASS isa Moose::Object will return an instance of CLASS.
Can you describe in more detail what you are trying to achieve, and why you think this is what you want? You probably want to build a factory class -- when you call a method on it, it will call the appropriate class's constructor and return that object to you, without you having to be concerned with the particular type you get back:
package MyApp::Factory::Repository;
sub getFactory
{
my ($class, %attrs);
# figure out what the caller wants, and decide what type to return
$class ||= 'Repository::_Sftp';
return $class->new(attr1 => 'foo', attr2 => 'bar', %attrs);
}
my $file = MyApp::Factory::Repository->getFactory(uri=>'sftp://blabla');