fREWdiculous!
3 Sep
I’m surprised I haven’t actually blogged this before. I had to do it recently for the first time in a long time and I figured I’d share the secret sauce.
At work we just added a complete permission system on top of our existing user system, but we didn’t want to make the UI as flexible as the underlying code. We ended up making a single role (which has all permissions) called “Full Control”. Without that role all you get is the stuff configured directly for your user; that is, your user gets a dashboard. So instead of making a grid of roles etc etc we just made a single checkbox on the user edit form. Of course I could have put in controller code to handle this special case, but I’m trying to get better at factoring code correctly. (As an aside: two years ago I would have also put all of this in the model; the frustrating thing is that Fat Model Skinny Controller only really works for relatively small apps. I’ll try to do a blog post on why I think that at another point later
)
Anyway, first off, here’s the full_control accessor I made:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Not a whole lot going on. If an argument is passed we set the user’s roles based on the truthiness of the argument. Because the system is currently just the one role we delete all roles for clearing it. Later on if we make the system more full featured we’ll have to change this up a bit of course. If no argument is passed we just return the count of full control roles, as that approximates truthiness just fine.
Next up are the “insert” and update wrappers. I quote insert because I actually override new:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | sub update { my ($self, $args, @rest) = @_; my $full_control = delete $args->{full_control}; my $ret = $self->next::method($args, @rest); $ret->full_control($full_control); return $ret } sub new { my ($self, $args, @rest) = @_; my $full_control = delete $args->{full_control}; $args->{user_roles} = [ { role => { name => 'Full Control' } } ] if $full_control; my $ret = $self->next::method($args, @rest); return $ret } |
The code for update should be abundantly clear. We just update the object, calling our accessor afterwards. The new code is a little bit more messy. Basically, instead of trying to use the accessor on new (which is wrong as new doesn’t actually imply an insert) we just leverage the excellent MultiCreate which DBIx::Class provides for us.
And that’s it! I hope this helps you get your job done that much faster/better
13 Apr
I’m just releasing my first new release of DBIx::Class::DeploymentHandler in six months! For the most part the release is just a few doc tweaks, but it does have one important new feature, the “_any” version.
If you didn’t already know, DBICDH has a handy little directory structure for how your deploys work. If you haven’t seen it, take a look. This new release allows you to use _any in place of a version or version set, which will run the given files no matter what version you are deploying to.
Enjoy!
15 Mar
Thanks to some idle chatting in the #dbix-class channel on irc.perl.org I came up with DBIx::Class::Helper::Row::RelationshipDWIM. The gist of it is that you get to type
1 | __PACKAGE__->has_many(addresses => '::Address', 'person_id' ) |
instead of
1 | __PACKAGE__->has_many(addresses => 'MyApp::Schema::Result::Address', 'person_id' ) |
That yields a total sugar (with candy) of the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | package Lynx::SMS::Schema::Result::MessageParent; use Lynx::SMS::Schema::Candy; primary_column id => { data_type => 'int', is_auto_increment => 1, }; column account_id => { data_type => 'int' }; column type_id => { data_type => 'int' }; column caller_id => { data_type => 'int', size => 11, is_nullable => 1, }; column message => { data_type => 'nvarchar', size => 1000, }; column when_created => { data_type => 'datetime', set_on_create => 1, }; column voice_id => { data_type => 'int', is_nullable => 1, }; belongs_to account => '::Account', 'account_id'; belongs_to voice => '::Voice', 'voice_id'; belongs_to type => '::Type', 'type_id'; has_many children => '::MessageChild', 'message_parent_id'; 1; |
Pretty nice.
9 Mar
I’m extremely proud to announce a fairly major release of DBIx::Class::Candy, 0.002000. Not only are the tests much more complete as well as the underlying code much more comprehensible, but the usage of the Candy can now be even sweeter.
To get the full features of DBIx::Class::Candy you’ll want to first create the following base class:
(Of course you can call this sugar if you hate my naming scheme or rainbows if you love it.)
1 2 3 4 5 6 7 8 9 | package MyApp::Schema::Candy; use parent 'DBIx::Class::Candy'; sub base () { 'MyApp::Schema::Result' } sub perl_version () { 12 } sub autotable () { 1 } 1; |
Now a basic id, name table would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package MyApp::Schema::Result::Permission; use MyApp::Schema::Candy; primary_column id => { data_type => 'int', is_auto_increment => 1, }; unique_column name => { data_type => 'varchar', size => 30, }; 1; |
id got set to the pk, name got a unique constraint, the table was named permissions, perl 5.12 features were imported, the base class was set to MyApp::Schema::Result. How awesome is that! Not that you can do the same thing as above without a subclass if you like still:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package MyApp::Schema::Result::Permission; use DBIx::Class::Candy -base => 'MyApp::Schema::Result', -perl5 => v12, -autotable => v1; primary_column id => { data_type => 'int', is_auto_increment => 1, }; unique_column name => { data_type => 'varchar', size => 30, }; 1; |
I should give credit where credit is due. Getty had lots of ideas for improvements, but the first one I implemented (due to how easy it was and how much I liked it) was primary_column. mst had the idea of automatically generating the table name and using a subclass of candy to avoid boilerplate. Enjoy!
31 Jan
I just released a new version of DBIx::Class::Helpers and it has two new components: DBIx::Class::Helper::ResultSet::ResultClassDWIM and DBIx::Class::Helper::Schema::GenerateSource.
This component solves an issue I’ve seen both by myself and with my coworkers; it’s too hard to remember/type the following:
1 2 3 | my $rs = $schema->resultset('Foo')->search($q, { result_class => 'DBIx::Class::ResultClass::HashRefInflator', }); |
So I wrote this component which will let you generically write:
1 2 3 | my $rs = $schema->resultset('Foo')->search($q, { result_class => '::HashRefInflator', }); |
or use the specially hardcoded:
1 2 3 | my $rs = $schema->resultset('Foo')->search($q, { result_class => '::HRI', }); |
Handy right?
This component is a little more unusual. The idea is to take care of some of the issues I mentioned here. It doesn’t solve everything due to some design issues in DBIx::Class, which I hope to take care of soon. Basically the idea is that instead of the boilerplate files that you get when you use DBIx::Class::Helper::Row::SubClass, you can instead just do the following in your schema:
1 2 3 4 5 6 7 8 9 | package Foo::Schema; __PACKAGE__->load_components('Helper::Schema::GenerateSource'); # ... __PACKAGE->generate_source(User => 'MyCompany::Result::User'); 1; |
The main issue is that even though this correctly associates a new source to the schema, you cannot currently add relationships to the source. I’ll make another post when I fix that, but I doubt I’ll get it into the next release of DBIx::Class.
In other news I’m doing some pretty sweet stuff with the SQL generation code in DBIx::Class and I hope it will be ready for the next release. I’ll post more when that’s released.
28 Dec
At work we have a tiny set of classes and relationships that we’ve reused for a few projects now. The idea is that it’s a package deal of users, roles, permissions, and a way to map permissions to parts of the application. I’m actually pretty fond of it, but its usage is a little awkward and not very flexible. If I could I’d put it on CPAN as that would mean tests, docs, and more importantly, a way to make it more useful for disparate projects.
The way it works right now is as follows:
1 2 3 4 5 6 7 8 9 10 11 | package Lynx::SMS::Schema::Result::User; use strict; use warnings; use parent 'MTSI::Schema::Result::User'; __PACKAGE__->load_components('Helper::SubClass'); __PACKAGE__->subclass; 1; |
for all of the seven-ish results. That’s a little obnoxious, but you need a place to add extra columns and relationships. The problem is if you decide that you want to, say, not use roles the way we decided to, you already have all these bogus relationships tying everything together. So what I’m thinking is that I’ll instead put the relationships in a more intelligent schema component with a bunch of options and whatnot. So you could do all of the above, and then in your schema do something like:
1 2 | __PACKAGE__->load_components('+MTSI::Schema::Auth'); __PACKAGE__->auth_setup; |
auth_setup could take a number of arguments toggling which results will get which permissions. Of course for a simple system one could just subclass the user result, which already has the password storage stuff that I’ve blogged about before. For a supercomplex system (maybe users can have roles and permissions?) all you’d need to do is add the relationships to the user and permission classes, and of course you could do that from the user, from the permission, or from the schema directly, since DBIx::Class is so flexible. Anyway, feel free to comment with other ideas, but I feel pretty good about this direction and hope to have a 0.00001 release soon.
10 Nov
I’ve kinda fallen off the blogging horse, but most of that is because I’ve been writing Open Source code in my freetime. I think generally that’s a worthwhile tradeoff, but I like blogging in general, when I have stuff to blog about, so I’m gonna try to mix in more blog posts; at least about what I’m doing.
The first thing I did was make a subclass of my DBIx::Class::Schema. It looks something like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | package A::SMSDB; use parent 'Lynx::SMS::Schema'; use String::Random qw(random_string random_regex); sub cat_init { my $self = shift; $self->deploy; $self->prepopulate; } sub init { my $class = shift; my $self = $class->connect('dbi:SQLite:dbname=:memory:'); $self->deploy; $self->prepopulate; return $self; } sub prepopulate { my $s = shift; $s->resultset('Type')->populate([ ['name'], ['SMS'], ['Phone'], ]); # there's more here, but you get the idea } sub populate_sms { my $self = shift; my $args = shift; state %bp = ( message_child_to_status_links => [{ status => { api_callback_id => '002',}, }], ); my $act = $args->{account}; my $amount = $args->{amount}; my $child_id = 1; $act->messages->create({ type => { name => 'SMS' }, message => 'THIS IS A TEST!', from_email => 'bogus@test.com', children => [map +{ phone_number => random_regex('1\d{9}'), to_name => random_regex('\w{20}'), child_id => $child_id++, %bp, }, ( 1 .. $amount )] }); } 1; |
This is generally a good idea even if you aren’t using Catalyst. Having a test sub-class of your schema allows you to put various helper methods on your schema just for testing, as well as handy ways to deploy the schema. Since I’m so early on with this project I’ve been redeploying the database a lot, so it’s been very helpful to have my tests all just call init at the start of the test.
Speaking of redeploying, I’ve also been using an in memory SQLite for my tests, which is very handy. At some point later I’ll switch it to the deployment database. To connect to an in memory database use “dbi:SQLite:dbname=:memory:” as your dsn and you’ll be golden.
The next thing I did was made a special catalyst config file just for tests. This is what it looks like right now:
1 2 3 4 5 6 7 8 9 10 | { "name":"Lynx::SMS", "Model::DB": { "schema_class":"A::SMSDB", "connect_info":{ "dsn":"dbi:SQLite:dbname=:memory:", "xdsn":"dbi:SQLite:dbname=testing.db" } } } |
First off, the changed schema_class means that now $c->model(‘DB’)->schema is my test schema, so I get all those extra methods. Also note the xdsn key in connect info. JSON doesn’t natively support comments, so this is my hacky way of putting in an alternate dsn which I use when I want to look at the database after the test has finished (and probably failed.)
Now to get the above to apply, first I save it to “lynx_sms_testing.json” and then, before loading up my catalyst app in the test, I put
1 2 | use lib 't/lib'; BEGIN { $ENV{LYNX_SMS_CONFIG_LOCAL_SUFFIX} = 'testing' } |
And then after loading catalyst I’ll do something along the lines of:
1 | Lynx::SMS->model('DB')->schema->cat_init; |
To generate and populate the database.
Of course a lot of this probably needs to start using some kind of fixture solution and put into a small module in t/lib so that I’m not copying around that BEGIN block in all of my controller tests, but this works for now and I’ll factor it into that once I have more than one controller test
28 Oct
Hello all,
I’m proud to announce DBIx::Class 0.08124! It’s been a VERY long time since 0.08123 and a this release brings lots of goodies.
My favorite is color-coded, correctly indented SQL, with placeholders filled in. Try it! Just do:
DBIC_TRACE_PROFILE=console DBIC_TRACE=1 ./foo.pl
There is also the exciting new “-ident” pseudofunction for SQL:
$rs->search({ foo => { -ident => ‘bar’ } })
which is the same as
$rs->search({ foo => \’bar’ })
but more introspectible!
Also, I’m a big fan of the new doc-map, which is at DBIx::Class::Manual::Features. Check it out!
The entire changelog is as follows:
0.08124 2010-10-28 14:23 (UTC)
* New Features / Changes
– Add new -ident “function” indicating rhs is a column name
{ col => { -ident => ‘othercol’ } } vs { col => \’othercol’ }
– Extend ‘proxy’ relationship attribute
– Use DBIx::Class::Storage::Debug::PrettyPrint when the
environment variable DBIC_TRACE_PROFILE is set, see
DBIx::Class::Storage for more information
– Implemented add_unique_constraints() which delegates to
add_unique_constraint() as appropriate
– add_unique_constraint() now poparly throws if called with
multiple constraint definitions
– No longer depend on SQL::Abstract::Limit – DBIC has been doing
most of the heavy lifting for a while anyway
– FilterColumn now passes data through when transformations
are not specified rather than throwing an exception.
– Optimized RowNum based Oracle limit-dialect (RT#61277)
– Requesting a pager on a resultset with cached entries now
throws an exception, instead of returning a 1-page object
since the amount of rows is always equal to the “pagesize”
– $rs->pager now uses a lazy count to determine the amount of
total entries only when really needed, instead of doing it
at instantiation time
– New documentation map organized by features
(DBIx::Class::Manual::Features)
– find( { … }, { key => $constraint } ) now throws an exception
when the supplied data does not fully specify $constraint
– find( col1 => $val1, col2 => $val2, … ) is no longer supported
(it has been in deprecated state for more than 4 years)
– Make sure exception_action does not allow exception-hiding
due to badly-written handlers (the mechanism was never meant
to be able to suppress exceptions)
* Fixes
– Fix memory leak during populate() on 5.8.x perls
– Temporarily fixed 5.13.x failures (RT#58225)
(perl-core fix still pending)
– Fix result_soutrce_instance leaks on compose_namespace
– Fix $_ volatility on load_namespaces (a class changing $_
at compile time no longer causes a massive fail)
– Fix find() without a key attr. choosing constraints even if
some of the supplied values are NULL (RT#59219)
– Fixed rels ending with me breaking subqueried limit realiasing
– Fixed $rs->update/delete on resutsets constrained by an
-or condition
– Remove rogue GROUP BY on non-multiplying prefetch-induced
subqueries
– Fix incorrect order_by handling with prefetch on
$ordered_rs->search_related (‘has_many_rel’) resultsets
– Oracle sequence detection now *really* works across schemas
(fixed some ommissions from 0.08123)
– dbicadmin now uses a /usr/bin/env shebang to work better with
perlbrew and other local perl builds
– bulk-inserts via $dbh->bind_array (void $rs->populate) now
display properly in DBIC_TRACE
– Incomplete exception thrown on relationship auto-fk-inference
failures
– Fixed distinct with order_by to not double-specify the same
column in the GROUP BY clause
– Properly support column names with symbols (e.g. single quote)
via custom accessors
– Fixed ::Schema::Versioned to work properly with quoting on
(RT#59619)
– Fixed t/54taint fails under local-lib
– Fixed SELECT … FOR UPDATE with LIMIT regression (RT#58554)
– Fixed CDBICompat to preserve order of column-group additions,
so that test relying on the order of %{} will no longer fail
– Fixed mysterious ::Storage::DBI goto-shim failures on older
perl versions
– Non-blessed reference exceptions are now correctly preserved
when thrown from udner DBIC (e.g. from txn_do)
– No longer disconnecting database handles supplied to connect
via a coderef
– Fixed t/inflate/datetime_pg.t failures due to a low dependency
on DateTime::Format::Pg (RT#61503)
– Fix dirtyness detection on source-less objects
– Fix incorrect limit_dialect assignment on Replicated pool members
– Fix invalid sql on relationship attr order_by with prefetch
– Fix primary key sequence detection for Oracle
(first trigger instead of trigger for column)
– Add various missing things to Optional::Dependencies
– Skip a test that breaks due to serious bugs in current DBD::SQLite
– Fix tests related to leaks and leaky perls (5.13.5, 5.13.6)
* Misc
– Entire test suite now passes under DBIC_TRACE=1
– Makefile.PL no longer imports GetOptions() to interoperate
better with Catalyst installers
– Bumped minimum Module::Install for developers
– Bumped DBD::SQLite dependency and removed some TODO markers
from tests (RT#59565)
– Do not execute t/zzzzzzz_sqlite_deadlock.t for regular module
installs – test is prone to spontaneous blow up
– DT-related tests now require a DateTime >= 0.55 (RT#60324)
– Makefile.PL now provides a pre-parsed DBIC version to the
Opt::Dep pod generator
– t/52leaks.t now performs very aggressive leak detection in
author/smoker mode
20 Sep
Thanks to arcanez, my color coding SQL Logging has been merged into DBIC’s master!
That means you can easily try out the new color coding! All you need to do to try it out is clone our master from git:
1 | git clone git://git.shadowcat.co.uk/dbsrgits/DBIx-Class.git |
Make sure you install any new deps. The main one will be SQL::Abstract 1.68.
1 | cpanm --installdeps . |
And then use that as your lib directory when you run your server or whatever:
1 | perl -I ~/DBIx-Class/lib scripts/foo_server.pl -rd |
Now, you won’t notice a difference till you set the DBIC_TRACE_PROFILE variable. It sets the color profile to use. If you are on Linux and install the ANSIColor package, you probably want to set it to “console”. If you are in win32 or do no want to install ANSIColor, set it to “console_monochrome”. Both profiles fill in placeholders for you, for excellent readability, so that’s extremely helpful.
If you would like to make a nicer colorscheme, or more importantly want to use modern 256 color consoles, feel free! The documentation for that is available at SQL::Abstract::Tree. The best way to define one of those is to make a json file (I do ~/dbic.json) and populate it with the profile information you like, and then set DBIC_TRACE_PROFILE to the full path of the file. That way you can experiment with various profiles and when you think you have one that’s worth sharing, send it to me and I’ll probably merge it in!
Anyway, we hope to cut a new release in a week or two, with lots of other great new stuff, so feel free to wait if you’d prefer.
29 Jul
I am proud to announce a new release of DBIx::Class::Helpers. There are five major changes in this release.
First off, the latest release adds DBIx::Class::Candy exports. So if you are using DBIx::Class::Candy to define a result, certain methods will be imported into your namespace. For example, DBIx::Class::Helper::Row::SubClass will export a subclass subroutine into your module. Not huge but nice nonetheless.
Next up, we have four shiny new components. Two are ResultSet components and two are Result components. One of the two ResultSet components was originally going to be in the core of DBIx::Class, but I decided to make a helper first to ensure that we iron out the details before we release it in core. That component is DBIx::Class::Helper::ResultSet::RemoveColumns. It does exactly what it sounds like. With it you can do
1 2 3 |
and the id column will no longer be selected in your ResultSet. I am sure that it has some quirks, but I am not sure what they would be till people use this. So have at it!
The next component, DBIx::Class::Helper::ResultSet::AutoRemoveColumns, is based upon RemoveColumns. Again, the name should make it clear what it does. Currently it removes typically large columns by default, like text, blob, and the like. See the docs for exactly what it removes. (Note: later on I hope to add a component that adds lazy columns as detailed in ovid’s post here. Ovid if you are reading this I can’t comment on your blog.)
Next up is a fairly simple component, DBIx::Class::Helper::Row::StorageValues. It gives you access to the last known stored value of a column. For example:
1 2 3 4 5 | my $foo = $resultset->search({ name => 'frew'}) ->next; $foo->name('frioux'); # prints "frew" say $foo->get_storage_value('name'); |
Building upon that we have my favorite new component: DBIx::Class::Helper::Row::OnColumnChange. This module adds powerful hooks for calling methods when a column has been modified. If you enable StorageValues for the column you hook into you get to look at the old value and the new value, which is pretty cool. There are three hooks: before_column_change, around_column_change, and after_column_change. It automatically takes into account values changing because of accessors as well as by the arguments passed to update. Also note that it allows you to tell it to wrap the call to update and the column change method in a transaction so that you can safely do things to other tables in the method. Anyway, enough talk, here’s a small example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | __PACKAGE__->add_column(relationship_status => { data_type => 'varchar', length => 30, keep_storage_value => 1, }); __PACKAGE->before_column_change(relationship_status => { method => 'happy_times', txn_wrap => 1, }); sub happy_times { my ($self, $old, $new) = @_; $self->significant_other->update({ feelings => 'happy' }) if $new eq 'together' && $old eq 'apart' } |
So basically if relationship status changes from apart to togehter the significant other gets marked as happy, and all of this is done in a transaction, which is pretty awesome.
Anyway, hopefully this makes your job easier. Have a good Friday!