1

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.

Rob
  • 781
  • 5
  • 19
  • You should really consider [DBIx::Class](http://search.cpan.org/~getty/DBIx-Class-0.08204/lib/DBIx/Class.pm) – jordanm Dec 22 '12 at 00:22
  • @jordanm Yes, I am using DBIx::Class. But that's irrelevant to what I am asking about. – Rob Dec 22 '12 at 01:14
  • Then I would use the `DBIx` classes for your methods. You can even use [DBIx::Class::InflateColumn](http://search.cpan.org/dist/DBIx-Class/lib/DBIx/Class/InflateColumn.pm) or override `create`, `update`, etc. – jordanm Dec 22 '12 at 02:12
  • "Base class be a role, and its methods take configuration arguments" makes sense to me. Why do you think that's messy? Also this sounds an awful lot like [DBIx::Class::SchemaLoader](https://metacpan.org/module/DBIx::Class::Schema::Loader). – Schwern Dec 22 '12 at 03:22

2 Answers2

2

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.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • It's nothing like SchemaLoader. I alread have DBIx::Class schemas for the tables. What I want are modules that keep the business logic of manipulating tables separate from the action controllers, and to keep it consistent across every access. The methods include data and the Catalyst context so that it logs changes, who changed them, updates last-modified times, normalize/validate data, and in some cases make changes to related tables. – Rob Dec 22 '12 at 14:59
  • I can't use these techniques, because the methods are in roles that use the base role. Some of these roles also use their sibling roles to manipulate related tables. So their configuration has to be private. – Rob Dec 22 '12 at 15:17
  • @Rob I don't understand the setup you're describing and why that means it has to be private. Also Perl's concept of "private" is very fuzzy, what do you mean when you say "private"? Who is meant to be setting up the configuration and who is meant to be using it? And could you explain what you mean by a "base role" and "sibling role"? It might be best to edit your post with this information rather than trying to squeeze it into a comment. – Schwern Dec 23 '12 at 07:35
  • by "private" I meant that nothing outside the module would see the configuration, and more importantly, that it would not affect the configuration of other roles. The configuration was to be set by the module author. Anyhow, as I mentioned in my answer below, MooseX::Role::Parameterized does exactly what I want. Thank you for your answers, though. – Rob Dec 24 '12 at 16:11
  • @Rob MX::Role::Parameterized is a great solution to your problem, better than what I posted. I hadn't realized its parameters were private! – Schwern Dec 24 '12 at 23:00
1

I think what I need are parameterized roles http://metacpan.org/pod/MooseX::Role::Parameterized

szabgab
  • 6,202
  • 11
  • 50
  • 64
Rob
  • 781
  • 5
  • 19