# $Id: /local/Mango/trunk/lib/Mango/Provider/Products.pm 1821 2007-08-10T01:46:18.172257Z claco $ package Mango::Provider::Products; use strict; use warnings; BEGIN { use base qw/Mango::Provider::DBIC/; use Mango::Exception (); use Scalar::Util (); __PACKAGE__->mk_group_accessors('component_class', qw/attribute_class tag_class/); __PACKAGE__->mk_group_accessors('inherited', qw/attribute_source_name tag_source_name/); }; __PACKAGE__->attribute_class('Mango::Attribute'); __PACKAGE__->attribute_source_name('ProductAttributes'); __PACKAGE__->tag_class('Mango::Tag'); __PACKAGE__->tag_source_name('Tags'); __PACKAGE__->result_class('Mango::Product'); __PACKAGE__->source_name('Products'); sub get_by_sku { my ($self, $sku) = @_; return $self->search({ sku => $sku })->first; }; sub search { my ($self, $filter, $options) = @_; $filter ||= {}; $options ||= {}; if (my $tags = delete $filter->{'tags'}) { my $count; if (@{$tags}) { foreach my $tag (@{$tags}) { if (Scalar::Util::blessed $tag) { if ($tag->isa('Mango::Tag')) { $tag = $tag->name; } else { Mango::Exception->throw('NOT_A_TAG'); }; }; if (!$count) { $count = 1; $filter->{'tag.name'} = $tag; } else { $filter->{'tag_' . $count . '.name'} = $tag; }; $count++ }; $options->{'distinct'} = 1; if (defined $options->{'join'}) { if (!ref $options->{'join'} || ref $options->{'join'} eq 'HASH') { $options->{'join'} = [$options->{'join'}]; }; } else { $options->{'join'} = []; }; push @{$options->{'join'}}, map {{'map_product_tag' => 'tag'}} @{$tags}; }; }; return $self->SUPER::search($filter, $options); }; sub create { my ($self, $data) = (shift, shift); my $attributes = delete $data->{'attributes'}; my $tags = delete $data->{'tags'}; my $product = $self->SUPER::create($data, @_); if ($attributes) { $product->add_attributes(@{$attributes}); }; if ($tags) { $product->add_tags(@{$tags}); }; return $product; }; sub add_attribute { my @attributes = shift->add_attributes(@_); return shift @attributes; }; sub add_attributes { my ($self, $product, @data) = @_; my $resultset = $self->schema->resultset($self->attribute_source_name); my @added; if (Scalar::Util::blessed($product)) { if ($product->isa('Mango::Product')) { $product = $product->id; } else { Mango::Exception->throw('NOT_A_PRODUCT'); }; }; foreach my $attribute (@data) { if (Scalar::Util::blessed $attribute) { if ($attribute->isa('Mango::Attribute')) { $attribute = {$attribute->get_columns}; } else { Mango::Exception->throw('NOT_A_ATTRIBUTE'); }; }; $attribute->{'product_id'} = $product; push @added, $self->attribute_class->new({ $resultset->update_or_create($attribute, {key => 'product_attribute_name'})->get_inflated_columns, meta => { provider => $self, parent => $product } }); }; return @added; }; sub search_attributes { my ($self, $product, $filter, $options) = @_; $filter ||= {}; $options ||= {}; if (Scalar::Util::blessed($product)) { if ($product->isa('Mango::Product')) { $product = $product->id; } else { Mango::Exception->throw('NOT_A_PRODUCT'); }; }; $filter->{'product_id'} = $product; my $resultset = $self->schema->resultset($self->attribute_source_name)->search( $filter, $options ); my @results = map { $self->attribute_class->new({ $_->get_inflated_columns, meta => { provider => $self, parent => $product } }) } $resultset->all; if (wantarray) { return @results; } else { return Mango::Iterator->new({ data => \@results, pager => $options->{'page'} ? $resultset->pager : undef }); }; }; sub delete_attributes { my ($self, $product, $filter) = @_; my $resultset = $self->schema->resultset($self->attribute_source_name); $filter ||= {}; if (Scalar::Util::blessed($product)) { if ($product->isa('Mango::Product')) { $product = $product->id; } else { Mango::Exception->throw('NOT_A_PRODUCT'); }; }; $filter->{'product_id'} = $product; return $resultset->search($filter)->delete_all; }; sub update_attribute { my ($self, $attribute) = @_; my $resultset = $self->schema->resultset($self->attribute_source_name); return $resultset->find($attribute->id)->update( {$attribute->get_columns} ); }; sub add_tag { my @tags = shift->add_tags(@_); return shift @tags; }; sub add_tags { my ($self, $product, @data) = @_; my $resultset = $self->schema->resultset($self->tag_source_name); my @added; if (Scalar::Util::blessed($product)) { if ($product->isa('Mango::Product')) { $product = $product->id; } else { Mango::Exception->throw('NOT_A_PRODUCT'); }; }; foreach my $tag (@data) { if (Scalar::Util::blessed $tag) { if ($tag->isa('Mango::Tag')) { $tag = {$tag->get_columns}; } else { Mango::Exception->throw('NOT_A_TAG'); }; } elsif (!ref $tag) { $tag = {name => $tag}; }; next unless $tag->{'name'}; my $newtag = $resultset->find_or_create($tag); $newtag->related_resultset('map_product_tag')->find_or_create({ product_id => $product, tag_id => $newtag->id }); push @added, $self->tag_class->new({ $newtag->get_inflated_columns, meta => { provider => $self, parent => $product } }); }; return @added; }; sub search_tags { my ($self, $product, $filter, $options) = @_; $filter ||= {}; $options ||= {}; if (Scalar::Util::blessed($product)) { if ($product->isa('Mango::Product')) { $product = $product->id; } else { Mango::Exception->throw('NOT_A_PRODUCT'); }; }; $filter->{'products'} = { 'id' => $product }; return $self->tags($filter, $options); }; sub delete_tags { my ($self, $product, $filter) = @_; my $resultset = $self->schema->resultset($self->tag_source_name); $filter ||= {}; if (Scalar::Util::blessed($product)) { if ($product->isa('Mango::Product')) { $product = $product->id; } else { Mango::Exception->throw('NOT_A_PRODUCT'); }; }; return $resultset->search( $filter )->related_resultset('map_product_tag')->search({ 'product_id' => $product })->delete_all; }; sub tags { my ($self, $filter, $options) = @_; $filter ||= {}; $options ||= {}; my $pfilter = delete $filter->{'products'} || {}; foreach my $key (keys %{$pfilter}) { next if $key =~ /^me\./; $pfilter->{"me.$key"} = delete $pfilter->{$key}; }; foreach my $key (keys %{$filter}) { next if $key =~ /^tag\./; $pfilter->{"tag.$key"} = delete $filter->{$key}; }; my @results = map { $self->tag_class->new({ $_->get_inflated_columns, meta => { provider => $self } }) } $self->resultset->search( $pfilter )->related_resultset('map_product_tag')->related_resultset('tag')->search( $filter, $options )->all; if (wantarray) { return @results; } else { return Mango::Iterator->new({ data => \@results }); }; }; 1; __END__ =head1 NAME Mango::Provider::Products - Provider class for product information =head1 SYNOPSIS my $provider = Mango::Provider::Products->new; my $product = $provider->get_by_id(23); =head1 DESCRIPTION Mango::Provider::Products is the provider class responsible for creating, deleting, updating and searching product information, including product tags and attributes. =head1 CONSTRUCTOR =head2 new =over =item Arguments: \%options =back Creates a new product provider object. If options are passed to new, those are sent to C. my $provider = Mango::Provider::Products->new; See L and L for a list of other possible options. =head1 METHODS =head2 add_attributes =over =item Arguments: $product, @attributes =back Adds the specified attributes to the specified product. C can be a Mango::Product object or a product id. C can be a list of attribute data hashes or Mango::Attribute objects. $provider->add_attributes(23, {name => 'Attribute', value => 'Value'}, $attributeobect, ...) =head2 add_attribute Same as C. =head2 add_tags =over =item Arguments: $product, @tags =back Adds the specified tags to the specified product. C can be a Mango::Product object or a product id. C can be a list of tag strings or Mango::Tag objects. $provider->add_tags(23, 'computer', $tagobect, ...) =head2 add_tag Same as C. =head2 create =over =item Arguments: \%data =back Creates a new Mango::Product object using the supplied data. my $product = $provider->create({ sku => 'ABC-1234', price => 2.34, description => 'The best product ever' }); print $role->name; In addition to using the column names, the following special keys are available: =over =item attributes This can be an anonymous array containing Mango::Attribute objects or hashes of attribute data (or both): my $product = $provider->create({ sku => 'ABC-1234', price => 2.34, description => 'The best product ever', attributes => [ {name => 'Attribute1', value => 'Value1'}, $attributeobject ] }); =item tags This can be an anonymous array containing Mango::Tag objects or tag strings (or both): my $product = $provider->create({ sku => 'ABC-1234', price => 2.34, description => 'The best product ever', tags => [ qw/computer linux/, $tagobject ] }); =back =head2 delete =over =item Arguments: \%filter =back Deletes products from the provider matching the supplied filter. $provider->delete({ id => 23 }); In addition to using the column names, the following special keys are available: =over =item user This can be a user id, or a user object for which this profile is assigned to. $provider->delete({ user => $user }); It is recommended that you use this key, rather than setting the foreign key column manually in case it changes later. =back =head2 delete_attributes =over =item Arguments: $product, $filter =back Deletes attributes matching the specified filter form the specified product. C can be a Mango::Product object or a product id. $provider->delete_attributes(23, {name => 'AttributeName'}); =head2 delete_tags =over =item Arguments: $product, $filter =back Deletes tags matching the specified filter form the specified product. C can be a Mango::Product object or a product id. $provider->delete_tags(23, {name => [qw/computer linux/]}); =head2 get_by_id =over =item Arguments: $id =back Returns a Mango::Product object matching the specified id. my $product = $provider->get_by_id(23); Returns undef if no matching product can be found. =head2 get_by_sku =over =item Arguments: $sku =back Returns a Mango::PRoduct object matching the specified id. my $product = $provider->get_by_sku('ABC-1234'); Returns undef if no matching product can be found. =head2 search =over =item Arguments: \%filter, \%options =back Returns a list of Mango::Product objects in list context, or a Mango::Iterator in scalar context matching the specified filter. my @products = $provider->search({ sku => 'A%' }); my $iterator = $provider->search({ sku => 'A%' }); In addition to using the column names, the following special keys are available: =over =item tags This can be an anonymous array containing Mango::Tag objects or tag strings (or both): my $products = $provider->search({ sku => 'A%', tags => [ 'computer', $tagobject ] }); =back See L for a list of other possible options. =head2 search_attributes =over =item Arguments: $product, $filter, $options =back Returns a list of Mango::Attribute objects in list context, or a Mango::Iterator in scalar context matching the specified filter. $provider->search_attributes(23, {name => 'A'%}); =head2 search_tags =over =item Arguments: $product, $filter, $options =back Returns a list of Mango::Tag objects in list context, or a Mango::Iterator in scalar context matching the specified filter. $provider->search_tags(23, {name => [qw/computer linux/]}); =head2 tags Returns a list of Mango::Tag objects in list context, or a Mango::Iterator in scalar context matching the specified filter. my $tags = $provider->tags; Only tags that are assigned to at least on product are returned. In addition to using the column names, the following special keys are available: =over =item products This is a hash containing a filter applied against products before returning those products tags. my $tags = $provider->tags({ products => { sku => 'A%' } }); =back =head2 update =over =item Arguments: $product =back Sets the 'updated' column to DateTime->now and saves any changes made to the product back to the underlying store. my $product = $provider->create(\%data); $product->price(10.95); $provider->update($product); =head2 update_attribute =over =item Arguments: $attribute =back Sets the 'updated' column to DateTime->now and saves any changes made to the product back to the underlying store. $attribute->value('AttributeValue'); $provider->update_attribute($attribute); =head1 SEE ALSO L, L, L, L, L, L =head1 AUTHOR Christopher H. Laco CPAN ID: CLACO claco@chrislaco.com http://today.icantfocus.com/blog/