Perl 로 메일을 보내려면 어떻게 해야할까요?

  저도 몰라서 근처 사는 최선생님께 물어봤습니다.

사용자 삽입 이미지

"메일, 그거 삶아먹으면 되는 거 아닙니까?"


.
.
.
라는 것은 훼이크고...

 Net::SMTP 라든가... CPAN 을 찾아보면 Mail 에 관련된 모듈은 널리고 널렸습니다.

 전 MIME::Lite 를 사용하기 때문에... 이쪽으로만 다루겠습니다. :-)

 사실 Korean Perl Workshop 참가신청을 만들 때, 메일 송신 스크립트 때문에 좀 고단했던 적이 있었습니다.
 메일서버도 없고, 사용할만한 건 Gmail 인데... CPAN 에서 Gmail 에 관련된 메일 송신 모듈이 제대로 동작하지 않는 것도 문제였죠. KPW 자체적인 메일 서버도 가지고 있지 않았구요. 시간은 촉박했고...
  설령 메일서버가 있다고 해도... 메이저 포털로 메일을 보낼 때 잘 안된다고 하는 그런 이슈도 있었구요.

 그때 한줄기 빛이 내려왔는데,, 그것이 SSMTP 라는 것입니다.
 
 SSMTP 가 무엇이냐는 자세한 설명은 여기에서 하지 않겠습니다. 구글해주세요.

 우분투에서 간단하게 SSMTP 패키지를 설치할때는...

sudo apt-get install ssmtp

 로 설치가 가능합니다.

  그리고는 /etc/ssmtp/ssmtp.conf 에 아래의 설정을 넣습니다.

root=your.email@gmail.com
mailhub=smtp.gmail.com:587
useSTARTTLS=YES
AuthUser=your.email@gmail.com
AuthPass=your.password
rewriteDomain=gmail.com
FromLineOverride=YES
hostname=blah

 그래서 메일계정은 gmail 을 사용하기로 하고 위와같이 설정했습니다. Gmail 을 사용하면 어디든지 메일을 보낼 수 있기 때문이었죠. Gmail 을 스팸처리하는 곳은 없을 테니...
  MIME::Lite 모듈이 없으면 설치해주세요.
 
use strict;
use warnings;
use MIME::Lite;

my $msg = MIME::Lite->new(
    'Return-Path' => 'sender.email@gmail.comr',
    'From'        => 'sender.email@gmail.com',
    'To'          => 'receive@email.com',
    'Subject'     => 'saillinux 산 버터, 어떻게 생각하시나요?',
    'Charset'     => 'utf-8',
    'Encoding'    => '8bit',
    'Data'        => '사실 그거 삶아먹으면 괜찮은거 아닙니까?'
    );
$msg->send;

 이렇게 스크립트 하나 만들어서 실행시키면...

사용자 삽입 이미지

 이렇게 메일이 오게됩니다. +_+

 뭐, 파라메터 넘겨서 From ,To 바꾸거나, 제목 바꾸거나... DB 에서 긁어와서 집어넣거나... 혹은 Template 를 사용해서 메일 내용을 꾸미거나... 하는 방법도 있겠죠.

 여러가지 다양한 부수적인 내용은 직접 해보시면 됩니다.
 현재 KPW 사이트에서는 KPW::Mail 이라는 Wrapper 모듈을 만들어 두고 사용하고 있습니다. Template 도 만들어서 Mail 내용등은 완전히 분리시켜두고 말이죠.

  Daum이나 Yahoo 에서는 Subject 가 깨지는 현상도 있다고 하는데요. keedi 님께서 이에 대한 처치방법을 알려주셨네요.
  use MIME::QuotedPrint qw(encode_qp);
  use Encode qw(encode);

 필요한 모듈을 use 해서... Subject 를

...
Subject => '=?UTF8?Q?' . encode_qp(encode('utf-8', "블라블라"), '') .'?=',
...
 
 이런식으로 감싸면 된다는 군요. keedi++

 (SSMTP 는 jachin 님으로부터 줏어들어서 saillinux 님께서 설정해주셨습니다. jachin++, saillinux++)


  :: 이상 IRC #perl-kr 에서 h0ney 님께서 "메일 어케날려요?" 라는 떡밥을 날리셔서 썼습니다.
  :: (떡밥주도 블로그 포스팅 전략!!)
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by JEEN
사용자 삽입 이미지

 2주후, 8월 23일에 드디어 Korean Perl Workshop 이 개최됩니다(물론 전 못가지만...).

 세션은 오후 1시부터 저녁 9시까지의 세션이니  8시간입니다. 일반적으로 대충 3-4시간하고 끝나는 중소규모의 컨퍼런스나 Tech Talk 와는 달리 8시간이면.. 꽤나 하드코어한 것이죠.  하드코어한만큼 좋은 내용들이 즐비합니다.

 생물학, 언어학같은 생소한 부분을 비롯해서... Perl 을 이용한 보안/해킹, GUI 개발, 웹 개발...
 상당히 넓은 장르를 한꺼번에 소화할 수 있는 기회가 될 것 같습니다.

 거기에 한국에서는 아마도 처음으로 열리는 Perl 만을 위한 워크샵이니 많은 기대를 하고 있습니다. KPW2008 의 주제에 맞게 다시금 Perl 이 떠올라서 CGI 로만 결부되는 인식을 무참하게 깨뜨렸으면 하네요.

 자세한 정보는 KPW2008 홈페이지 를 참조하세요.

(사실 예전에 있었던 2008/06/18 - [IT/Perl] - * 제 1회 Korean Perl Workshop : Rising Perl 개최 * , 이것이 연기된 것입니다.)
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by JEEN

 현재 개발중인 프로젝트에 쓰이는 내용을 살짝 다뤄볼까 합니다.

 음... 테이블의 칼럼 수가 많거나, 적거나.. 아직 정해지지 않아서 나중에 추가될 지도 모른다거나... 이런 불안함에서 제대로 테이블을 만들더라도, 나중에 ALTER TABLE 의 폭발을 겪게 되는 경우가 있었습니다. 컬럼을 추가하거나, 지우거나, 바꾸거나 하면서 불안함이 있다는 것이죠. 그래서 YAML 을 그대로 집어넣게 되었습니다.

 이럴때 가볍게 쓸 수 있는 것이 inflate_column 입니다.
 위의 얘기는 잠시 접어두고 그럼 예제를 볼까요? MyTest::User 에 다음과 같은 것을 추가했습니다.

 __PACKAGE__->inflate_column(<br />    user_id => {<br />        inflate => sub {<br />            my ($value, $obj) = @_;<br />            # $obj->$column_name 으로 User Schema Class 의 컬럼을 참조할 수 있습니다.<br />            sprintf "%04d", $value;<br />        },<br />        deflate => sub {<br />            my ($value, $obj) = @_;<br />            $value;<br />        },<br />    });

  이것으로 무엇을 하느냐...

  User 의 user_id 컬럼을 지정해서 user_id 를 처음부터 건드려놓은 상태로 내놓거나(inflate), Table 에 입력을 할 시에도 마찬가지로 입력 직전에 건드린 상태로 쓰기를 수행(deflate) 하는 겁니다.

  위의 예제에서는 User 의 user_id 를 zero-fill 합니다. 1 이라면 0001 이라든가.. 이런 형식으로 나오겠죠?

my $user = $schema->resultset('User')->search({});

while(my $row = $user->next) {
    print $row->user_id."\t".$row->name."\n";
}

  그럼 한번 뽑아볼까요?
사용자 삽입 이미지
  자, 이렇게 나오네요.

  이렇게해서, 예를들면 datetime 의 data type 을 가지고 있는 created_on, updated_on 은 DateTime 인스턴스로 만들어 줄 수도 있겠죠.

  제가 사용하는 경우는 ...

CREATE TABLE test (
   no    INT NOT NULL,
   attributes   TEXT NOT NULL,
   created_on DATETIME NOT NULL,
   updated_on DATETIME NOT NULL,
   primary key(no)
 );

  이런 Schema 가 있다면...

use YAML;
__PACKAGE__->inflate_column(
  attributes => {
     inflate => sub {
        my ($value, $obj) = @_;
        YAML::Load($value);
      },
      deflate => sub {
        my ($value, $obj) = @_;
        YAML::Dump($value);
       },
});

  이렇게 inflate_column 을 설정해두었습니다. 그리고 이런식으로 등록합니다.

 my $obj = {
    irc => {
       perl => {
         saillinux => {
           envy => 'yuni',
           attack => 'JEEN',
           love  => [ 'ero', '하얀_고양이', 'Fate' ],
          },
        },
    },
 };
 
 $schema->resultset('Test')->create( attributes => $obj );

 이렇게 등록된 데이터는 YAML 형식으로 보관되어 있을 겁니다.
 그리고 빼내서 쓸려면 이런식으로 합니다.
 
my $rs = $schema->resultset('Test')->search({})->first;
print $rs->attributes->{irc}->{perl}->{saillinux}->{envy};  # yuni
print for (@{ $rs->attributes->{irc}->{perl}->{saillinux}->{love} }); # ero 하얀_고양이 Fate

  이렇게 사용합니다. :-)

  inflate_column 를 사용하지 않는 경우에는 집어넣을 때는 매번 YAML::Dump 하고...
  뺄때는 매번 YAML::Load 해야 되겠죠.

  이렇게 가볍게 InflateColumn 에 대해서 알아보았습니다.
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by JEEN

 자, 그럼 Relation 에 대한 이야기입니다.

 Relation 은 말그대로 관계입니다. 뭐에 대한 관계냐면, Schema Class 간의 관계이죠.
 
 User, Company, Deathnote 에서 Deathnote 는 User 의 user_id 와 Company 의 company_id 를 가지고 있습니다.
 그럼 어떻게 Deathnote 에서 User 의 name 을 알 수 있을까요?
 ( SQL 스럽게 하면 애들장난같은 얘기라서 접어두겠습니다만 )

  아, 그전에 일단 테스트 용으로 데이터를 집어넣겠습니다.

my $deathnote = $schema->resultset('Deathnote');

while(<DATA>) {
    chomp;
    my @data = split/,/, $_;
    $deathnote->create({
        user_id => $data[0],
        company_id => $data[1],
    });
}

__DATA__
1,1
2,2
3,3
4,4
5,5
6,6
7,7

  자.. 어떻게 할 것인가... 가장 간편하게 이런 방법이 있습니다.
my $deathnote = $schema->resultset('Deathnote')->search({});

while(my $row = $deathnote->next) {

    my $user = $schema->resultset('User')->find($row->user_id);
    print $user->name if $user;
}
 흠? 근데 이건 좀 너무하다 싶지 않나요?

 이렇게 생각하면 Relation 이 나올 때입니다.
 MyTest/Deathnote.pm 을 보겠습니다. 아, 다른 건 다 집어치우고... 그냥 추가된 것만 보죠.

__PACKAGE__->has_one( user => 'MyTest::User', { 'foreign.user_id' => 'self.user_id' } );
__PACKAGE__->has_one( company => 'MyTest::Company', { 'foreign.company_id' => 'self.user_id'
} );

 이렇게 추가했습니다.
  Deathnote 에서 User 로 연결하기 위해서 user 라는 키를 사용하고,
User 와 Deathnote 는  user_id 로 연결된다는 내용입니다. (has_one 이라는 것은 말 그대로 1:1의 관계입니다)
  그리고 Company 역시나 마찬가지입니다.
  이렇게 Relation 을 지정해두면 어떻게 되려나요?

 my $deathnote = $schema->resultset('Deathnote')->search({});

while(my $row = $deathnote->next) {
  print $row->user->name."¥n";
}

  이런 형식으로 간단하게 user의 name 을 얻어올 수 있습니다.
.
.
.
  정말로 그럴까요? 두번째 장에서 하얀_고양이라는 User 를 삭제했기 때문에 세번째 Deathnote 에서는 에러가 발생합니다.

  can't call method "name" on an undefined value at ..

 그러면 이렇게 해주면 됩니다. user 가 있으면 찍어라 라고...

   print $row->user->name if $row->user;

  간단하죠;;;

 근데 User 가 어떤 Company 인지를 알고 싶습니다. 근데 이 정보는 Deathnote 에만 있어요.
 User->Deathnote->Company 식으로 넘어가야되는데요...

  우선은 MyTest/User.pm 을 열어서 한 줄 추가합니다.

__PACKAGE__->belongs_to( note => 'MyTest::Deathnote', { 'foreign.user_id' => 'self.user_id' });

  belongs_to 라는 것은 말 그대로입니다. :-) Deathnote 에 User 가 has_one 으로 Relation 관계를 성립하고 있습니다. belongs_to 로 Deathnote 에 속하게 됩니다. (근데 has_one 으로도 Relation 관계가 성립되더군요;;; 뭔가 일방적인 관계일지도..)

  그러면 User 에서 Company 의 name  을 읽어 볼까요?

my $user = $schema->resultset('User')->search({});

while(my $row = $user->next) {
   
    print $row->name .'=>'.$row->note->company->name."\n" if $row->note && $row->note->company;

}

  이렇게 하면 되겠죠?

  근데 여기서 잠깐... "언제까지 전체 레코드를 읽어올 것인가요. 제대로 SQL 조건도 좀 주고.. 이러고 싶은데.." 라고 saillinux 님께서 사장님 마인드로 또 말씀하십니다.

  그러면서 던져주신 조건이란게 << user_id 가 3 을 넘고, company_id 가 2 를 넘는 것 >> 이라고 하네요.
 근데 User Schema Class 를 사용하라고 합니다. 어라... 분명 User Schema Class 에는 company_id 가 없는 데 말이죠. 역시 사장님 마인드입니다.

  "거기다가 company_id 순으로 정렬하고, 그 중에 딱 2개만 보고 싶어요" 라고 하시는 군요...
  오늘 제대로 뽕을 뽑으시는 군요.

   요건 정의를 다시 해보겠습니다.

    "user_id" 가 3 을 넘고,
    "company_id"  가 2 를 넘고,
    "User" Schema Class 를 사용하고,
    "company_id" 순으로 정렬하고
    "2개" 만 출력한다 입니다.
.
.
.
.
  답은 이렇습니다.  

my $user = $schema->resultset('User')->search({
    'me.user_id' => { '>' => 3 },
    'company.company_id' => { '>' => 2 },
},{
    prefetch => [ { 'note' => 'company' } ], 
    order_by => 'company.company_id asc',
    rows => 2,
});

while(my $row = $user->next) {
    print $row->name. '=>' .$row->note->company->name."\n";
}

  User Schema Class 에서 Company 와는 어떤 관계도 아닙니다. 그럴려면 Deathnote 를 통해서 Company 에 접근할 수 있습니다.
 
   prefetch => [ { 'note' => 'company' } ]

  MyTest::User 에서 note 로 Deathnote 에 갈 수 있고, Deathnote 는 company 로 Company에 접근할 수 있습니다.  Relation 관계는 이미 Schema Class 에서 정의해줬기 때문에 이렇게 간단하게 접근할 수 있습니다.

    order_by => 'company.company_id asc',

  company_id 순이라면 위의 prefetch 에서 이미 길을 텄기때문에 "company.company_id"  로 접근할 수 있습니다.(SQL::Abstract 를 참조하세요)
 
      rows => 2

  단 두줄만 뺀다는 겁니다. MySQL 의 LIMIT 2 같은 사용법이죠.

  물론 Relation은 might_have, has_many 같은 것들도 있습니다. 자세한 내용은 문서를 참조하세요. :-)

  이런저런 빼고 넘어가는 부분들이 많은지라... 조금만 문서를 보시면 금방 아실만한 내용들이 대부분일 겁니다. 모르시면 같이 한번 궁리해보죠 :-)

  흡.. 빠진 부분들(제가 아는 한도에서)은 다음에 DBIC 를 정리하는 차원에서 가볍게 짚고 가도록 하겠습니다.


이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by JEEN
  이번에는 Component 에 대해서 알아보겠습니다. 일단 예제부터 보고 시작하죠.

  Mytest/Company.pm 을 열어봅니다.

package MyTest::Company;

use strict;
use warnings;

use base 'DBIx::Class';
use AutoStoreDateTime;

__PACKAGE__->load_components("PK::Auto", "+AutoStoreDateTime", "Core");
__PACKAGE__->table("company");
__PACKAGE__->add_columns(
  "company_id",
  { data_type => "INT", default_value => undef, is_nullable => 0, size => 11 },
  "name",
  { data_type => "VARCHAR", default_value => "", is_nullable => 0, size => 255 },
  "created_on",
  { data_type => "DATETIME", default_value => "", is_nullable => 0, size => 19 },
  "updated_on",
  { data_type => "DATETIME", default_value => "", is_nullable => 0, size => 19 },
);
__PACKAGE__->set_primary_key("company_id");


# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-08-05 16:39:27                      
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:DHRIea3OvvtGG+yhaBVTvQ                        


# You can replace this text with custom content, and it will be preserved on regeneration    
1;

 이 중에서 제가 살짝 추가한 부분이 있습니다.

use AutoStoreDateTime;
__PACKAGE__->load_components("PK::Auto", "+AutoStoreDateTime", "Core");

 바로 이부분에 PK::Auto 라는 component 도 불러오라고 지정했습니다. PK::Auto 는 온라인 게임에서 자동으로 PK 를 수행하는   Primary Key Auto Increment 라고 보시면 됩니다. 즉 이 Schema Class 에서는 company_id 가 Primary Key 이니까 자동으로 Auto Increment 해준다는 거죠. (물론 SQL 에서 Auto Increment 를 지정해주면 DBMS 자체적으로 이 행위를 수행하지만, auto increment 가 지정되지 않은 경우에는 PK::Auto가 알아서 해줍니다. 이것을 확인하기 위해서 직접 Company 테이블을 변경해보시길 바랍니다)

  그리고 AutoStoreDateTime 도 있습니다. 근데 이 앞에 +라고 붙었는데.. 이것은 Custom component 입니다. PK::Auto 같은 경우는 DBIx::Class::PK::Auto 라는 모듈을 불러오는 것인데, AutoStoreDateTime 은 DBIC 정식 모듈이 아닌... 여기서 만들어서 쓰겠다라는 것이죠.
use AutoStoreDateTime;

use strict;
use warnings;
use base 'DBIx::Class';

use DateTime;

sub insert {
    my $self = shift;
   
    $self->created_on( DateTime->now( time_zone => 'Asia/Seoul' ) )
                             if $self->result_source->has_column('created_on');
                      
    $self->next::method(@_);
}

sub update {
    my $self = shift;

    $self->updated_on( DateTime->now( time_zone => 'Asia/Seoul' ) )
                           if $self->result_source->has_column('updated_on');
    $self->next::method(@_);
}

1;

  이와 같은 컴포넌트 모듈을 만들었습니다. 이것을 AutoStoreDateTime.pm 이라고 저장하고 MyTest.pm 과 같은 디렉토리에 놓습니다.
  그럼 어떤 결과를 보이는 지 확인해 볼까요?
my $company = $schema->resultset('Company');

while(<DATA>) {
    chomp;
    $company->create({
        'name'    => $_,
    });
}

__DATA__
I company
N company
D company
E company
A company
Y company

  이렇게 Company.name 을 만들도록합니다. company_id 도 지정하지 않았고, created_on 도 지정하지 않았습니다. 등록날짜도 모르면 어떻게 하려고 이럴까요?
사용자 삽입 이미지
  하지만 이렇게 created_on 은 create 한 시점의 날짜/시간을 가지고 있습니다. company_id 도 물론 지정하지 않았는데 이렇게 생겼네요(SQL에서 auto increment 가 있으면 뭐 그게 그거입니다만).

 근데 사장 마인드를 가지신 saillinux 님께서 짝수 회사번호를 가진 회사는 전부 소문자로 표기하고 싶다는 얼토당토 않은 요구를 하고 있습니다. 어쩔 수 있나요. 까라면 까야지..

my $company = $schema->resultset('Company')->search({});

while(my $row = $company->next) {
    if ($row->company_id % 2 == 0) {
        $row->name(lcfirst $row->name);
        $row->update;
    }
}

  이렇게 하면 되겠죠?
사용자 삽입 이미지
  그러면 update 된 레코드만 updated_on 이 지정되게 되죠.  어때요? 참 쉽죠?

 이렇게 DBIC 의 Component 활용에 대해서 알아보았습니다. :-) 직접 Component 를 만들어서 보다 편하게 DB 를 주물러 보심은 어떠실런지요?

  다음에는 Relation 에 대해서 살짝 알아보겠습니다. 

* 참고
 - **** Project



이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by JEEN

  그럼, DBIC::Schema::Loader 로 만든 Schema Class 는 어떻게 사용하는 것일까요?

use strict;
use warnings;
use MyTest;  # 앞서 정의한 Schema Class
use DateTime;

my $schema = MyTest->connect(
    'dbi:mysql:mytest',
    'root',
    'PASSWORD');  # DBI 의 connect_info 랑 같습니다.

my $user = $schema->resultset('User');

while(<DATA>) {
    chomp;
    my @data = split /,/, $_;
    $user->create({
        'user_id' => $data[0],
        'name'    => $data[1],
        'created_on' => $dt->ymd. ' '. $dt->hms,
    });  # INSERT INTO..
}

__DATA__
1,JEEN
2,saillinux
3,하얀_고양이
4,a3r0
5,yuni
6,keedi
7,pung96
8,song
9,ssie
10,amorette

  이 스크립트를 MyTest.pm 과 같은 장소에 놓으시고... 적당한 이름으로 저장해서 실행해봅니다.
  아무것도 안나올겁니다. 그렇다면... DB 를 확인해볼까요?
사용자 삽입 이미지
이렇게 제대로 들어가 있네요 :-)
그러면 이렇게 입력된 데이터를 가지고 놀아 볼까요?

use strict;
use warnings;
use MyTest;
use DateTime;

my $schema = MyTest->connect(
    'dbi:mysql:mytest',
    'root',
    'PASSWORD');

my $user = $schema->resultset('User')->search({
    'user_id' => { '<' => 5 },
});  # SELECT * FROM user WHERE user_id < 5;

while(my $row = $user->next) {
 print $row->name."\n";
}
 위의 주석처럼 user_id 가 5보다 작은 사람들을 불러모읍니다. :-)
 JEEN
 saillinux
 하얀_고양이
 a3r0
 결과는 이처럼 나오겠죠?
 
  ->search 로 데이터를 줏어올 때는 기본적으로 복수로 가정합니다. 그러니 $user->next 같은 것으로 루프를 돌려주는 것입니다. $user->all 같은 것을 사용하면 말 그대로 전체 데이터를 ARRAY 로 받습니다.

 여기에서 생각난 예제:
 근데 전 하얀_고양이 님을 별로 좋아하지 않습니다. 그래서 지워버리고 싶어요.

use utf8;

my $user = $schema->resultset('User')->find({
    'name' => '하얀_고양이',
});    # find 를 사용할 경우 하나의 레코드만을 얻어옵니다.

print $user->name;
$user->delete;
 
  혹은
use utf8;
my $user = $schema->resultset('User')->search({});

while(my $row = $user->next) {
  $row->delete if $row->name eq '하얀_고양이';  # 이렇게도 삭제할 수 있죠 :-)
}

  이렇게 해서 하얀_고양이님을 지웠습니다.
  그러니 IRC #perl 에 계신 분들이 모두 기뻐하셔서 빵글이를 붙이고 싶다는 데요. :-)
  그럼 어떻게 할까요?
my $user = $schema->resultset('User')->search({});

while(my $row = $user->next) {
    $row->name($row->name . '_^^');
    $row->update;
}
 
사용자 삽입 이미지

 과연.. 모두들 기뻐하시는 군요 :-)

 이런식으로 직감적으로 DB의 데이터를 조작할 수 있는 것이 바로 DBIC 의 강점입니다.
 이에관한 예제는 DBIx::Class 페이지에서 쉽게 찾아볼 수 있고, 보다 다양한 예제를 제공해줍니다.
  그리고 보다 자세한 SELECT 구문에 대한 예제는 앞으로도 계속 적어나가도록 하겠습니다.

  다음번에는 Component 에 대해서 간단하게 알아보겠습니다. 

이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by JEEN
: 요 근래 잠깐 깜빡하고 Catalyst 에 관한 글을 쓰지도 못하고 있었네요.

일단 그전에 미리 Template Toolkit 같은 Template Engine 이나
DBIx::Class 같은 ORM 같은 것을 알아놓고 가는 게 좋겠다는 생각에...
잠시 얘기가 빙빙 겉돌고 있습니다.

 일단 DBIx::Class, 통칭 DBIC 라고 불리는 것부터 알고 나중에 차근차근 Catalyst 내용을 이어가도록 하겠습니다.

 ORM 이라고 아시죠? Object-Relation Mappings 라고...
 자세한 내용은 검색해보시면 짠 하고 나올겁니다. ( 검색 예 )

 Perl 에서 ORM 을 얘기하면 DBIC 가 대표적인 예가 될 것입니다. 그외 여러가지 있지만 안써봐서 생략합니다.

 DBIC 를 제대로 사용하려면 Schema 클래스를 만들어야 합니다.

 만들어야한다에 대한 제대로 다시 정의를 하자면, "만들게 해야합니다" 입니다.

 DBIx::Class::Schema::Loader 가 이 일을 해주는데요. 그런고로 한번 만들게 해보겠습니다.
 CREATE TABLE user (
   user_id  int not null auto_increment,
   name    varchar(255) not null,
   created_on datetime not null,
   updated_on datetime not null,
   primary key(user_id)
 );

 CREATE TABLE company (
   company_id int not null auto_increment,
   name           varchar(255) not null,
   created_on datetime not null,
   updated_on datetime not null,
   primary key(company_id)
 );

 CREATE TABLE deathnote (
   death_id int not null auto_increment,
   user_id   int not null,
   company_id int not null,
   created_on datetime not null,
   updated_on datetime not null,
   primary key(death_id)
 );

 이와같은 SQL 을 깨작거렸습니다. 우선 mytest 라는 DB 를 만들고 위의 user 와 company와 deathnote 라는 테이블을 생성합니다.

use strict;
use warnings;

use DBIx::Class::Schema::Loader (qw/make_schema_at/);

make_schema_at(
    'MyTest', {
        components => [],
        dump_directory => './',
    },
    \@ARGV,
    );

 그리고 이와같은 스크립트를 만들었습니다.(schema-loader.pl)

 perl schema-loader.pl dbi:mysql:mytest "mysql user" "mysql pass"

 그런 다음에는 이렇게 명령을 내립니다. (DBI 에서 connect_info 설정해주는 그것입니다)

Dumping manual schema for MyTest to directory ./ ...
Schema dump completed.
  그러면 이렇게 Schema dump 가 끝났다고 말해주죠.
 
사용자 삽입 이미지
  그러면 이처럼 Schema 클래스가 생성되게 됩니다.

package MyTest;

use strict;
use warnings;

use base 'DBIx::Class::Schema';

__PACKAGE__->load_classes;


# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-08-05 16:25:30        
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:P0OGC/gBKsOEyhe8yV+xsA          


# You can replace this text with custom content, and it will be preserved on re\
generation                                                                     
1;

  MyTest.pm 은 이처럼 썰렁합니다만..

package MyTest::User;

use strict;
use warnings;

use base 'DBIx::Class';

__PACKAGE__->load_components("Core");
__PACKAGE__->table("user");
__PACKAGE__->add_columns(
  "user_id",
  { data_type => "INT", default_value => undef, is_nullable => 0, size => 11 },
  "name",
  { data_type => "VARCHAR", default_value => "", is_nullable => 0, size => 255 },
  "created_on",
  { data_type => "DATETIME", default_value => "", is_nullable => 0, size => 19 },
  "updated_on",
  { data_type => "DATETIME", default_value => "", is_nullable => 0, size => 19 },
);
__PACKAGE__->set_primary_key("user_id");


# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-08-05 16:25:30                      
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:wV7hRdze6zdTrll+QAg4fw                        


# You can replace this text with custom content, and it will be preserved on regeneration    
1;

  MyTest/User.pm (MyTest::User) 는 이처럼 속이 꽉찼습니다. 처음에 SQL 로 정의해준 내용들이 그대로 정의되어 DBIC Schema 를 구성하게 됩니다.

  Catalyst 에서는 위의 스크립트(schema-loader.pl)를 쓸 필요없이...
./script/[App]_create.pl model MyTest DBIC::Schema MyTest create=static dbi:mysql:mytest *** ****
 이런식으로 하면 Schema Class 가 생성됩니다.

 다음에는 DBIC 를 이용해서 어떻게 데이터를 다루는 가에 대해서 알아보겠습니다.

 -- 참고
 * DBIx::Class
 * DBIx::Class::Schema::Loader
 
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by JEEN
사용자 삽입 이미지

 PHP 에는 Smarty 라는 것을 써봤는데, 요즘은 Template_ 라는 게 인기라더군요(s.*_cheeru님 曰)
 Perl 에는 Template Toolkit 이라는 녀석이 있습니다. Perl 의 대표적인 템플릿 엔진인데요.
 
 대부분 이런 템플릿 엔진을 사용하는 이유는, 역시 코드와 HTML 을 분리하기 위함이겠죠?
 아무래도 Perl 코드에 지저분하게 HTML 을 옹기종기 박아놓는 것은 저 역시도 맘에 들지 않습니다.

 요 근래의 업무중에 Feed를 만드는 일이  있었는데.. 여기에 Feed 의 Content 로 HTML 이 들어가는 경우가 있어서, Template Toolkit 을 가볍게 사용해보았습니다.

 (물론 Catalyst 같은 Perl 의 프레임워크의 기본적인 템플릿 엔진은 Template Toolkit 입니다. 여기서는 단지 프레임워크와는 동떨어져서 Template Toolkit 을 어떻게 하면 간편하게 이용하느냐에 초점을 맞추겠습니다.)

 일단 RSS Feed 를 만들기 위해 XML::Feed 라는 모듈을 사용합니다. 아, 물론 Template Toolkit 도 깔려 있어야되겠죠.

  - XML::Feed
  - Template

 위의 두 모듈을 CPAN 인스톨 합니다.
 물론 관련된 의존 모듈이 여러가지 깔리게 되므로... 다른 것은 언급하지 않겠습니다. :-) (XML::Atom 이라든가...)

 feed_gen.pl
use strict;
use warnings;
use DateTime;
use XML::Feed;
use Template;

my $feed = XML::Feed->new('Atom');

$feed->title("이빨까기 인형");
$feed->link("http://jeen.tistory.com/");
$feed->description("사이비 개발자의 이빨까기");
$feed->author("JEEN");
$feed->modified(DateTime::W3CDTF->parse_datetime(DateTime->now( time_zone => 'Asia/Seoul' ). '+09:00'));

# blahblah 어쩌고 저쩌고 해서 Feed 에 넣을 데이터 모집 :: 여기서는 그냥 파일로 왔다고 하겠습니다.

while(<>) {
  chomp;
  my @data = split /¥t/, $_;
  my $entry = XML::Feed::Entry->new('Atom');
  $entry->title($data[0]);
  $entry->link($data[1]);
  $entry->summary($data[2]);
  $entry->modified(DateTime->W3CDTF->parse_datetime(DateTime->now( time_zone => 'Asia/Seoul'). '+09:00'));
 $entry->issued(DateTime->W3CDTF->parse_datetime(DateTime->now( time_zone => 'Asia/Seoul'). '+09:00'));
  $entry->id($data[3]);
  my $content = XML::Feed::Content->new({ body => _make_content() });
  $entry->content($content);
  $feed->add_entry($entry);
}

open my $fh, ">", "test.xml";
print $fh $feed->as_xml;
close $fh;

sub _make_content {
  my $tt = Template->new;
  my $html;
  $tt->process('file.html', { action => 'attack', target => [qw/ saillinux whitecat /], }, ¥$html);
  $html;
}

 file.html (Template)
[% FOREACH d = target %]
[% d %] 를 [% action %] 하겠습니다.<br/>
[% END %]
 -- Perl방 평화유지위원회 JEEN --

 물론 코드의 테스트는 안해봤습니다. 즉석에서 그냥 이래저래 읊어봐서요. 문제가 있고, 수정을 해야된다면 그건 숙제로 남기겠습니다. :-)

 그러면 결과로는 test.xml 이 생성되고, 그 xml 은 위에서 언급한 포맷으로 구성이 되며, 적절한 RSS 리더로 읽어들이면 보일겁니다.

 일단 메인은 Template 에 대한 이야기니까... 이것에 대한 상세한 코드를 볼까요?
  my $tt = Template->new;
    # Template 의 인스턴스를 생성합니다.

  my $html;  # 템플릿 결과물을 담을 그릇이 됩니다.

  $tt->process('file.html', { action => 'attack', target => [qw/ saillinux whitecat /], }, ¥$html);
     # process 의 첫번째 인수는 템플릿 파일, 두번째 인수는 템플릿 변수(?) 입니다. 여기에서 템플릿에서 처리할 데이터들을 보내주면 되겠죠?, 세번째는 그 담길 그릇에 대한 레퍼런스가 필요합니다.

  print $html;  # 이 시점에서는 템플릿 결과가 나오게 됩니다.
  # saillinux 를 attack 하겠습니다.
  # whitecat 를 attack 하겠습니다.
  # -- Perl 방 평화유지위원회 JEEN --

 TT2 의 너무 간단한 사용법에 대한 이야기는 여기까지입니다.
 사실 TT2 하나 만으로도 이미 책 한권이 나와버렸거든요. 그만큼 문서화가 잘되어 있기 때문에 문제없이 사용하실 수 있으실 겁니다.
 
 이상입니다. 너무나도 허접한 코드와 대충대충 설명 죄송합니다. ;; 뭘해도 역시 문서를 읽는 수 밖에 없겠죠 ;-)

 충고나 질문등은 IRC 와 댓글을 이용해주시면 감사하겠습니다.

이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by JEEN
 흠... #1 에서 하나 만들고 #2 쓰는 시점에서 또 하나 CPAN 모듈을 추가 했습니다.
 물론 하기 쉬운 것만 샥샥 골라서 했으니까요. :-)

 Perlmani 스터디도 있고해서(하지만 참석은 못하니...)
 심심한 틈을 타서 슬라이드로 만들어 봤습니다.

 사실 이것도 완전한 것도 아니고.. 대~충 CPAN 모듈 만드는 생각에서부터  CPAN Author 가 되기까지의
 간단한 설명을 포함하고 있습니다.

 그리고 StoryQ 라는 국내 Slideshare 같은 서비스를 발견해서요. 그것을 써보겠습니다. :-)

 


 이번 포스팅은 이걸로 때우겠습니다. 저도 CPAN 모듈 만들면서 삑사리 낸거랑, 피드백 받은 것들이
다수 있고... 이것을 정리해서 다음에 또 올리도록 하겠습니다.
 잘하면 이런 서비스를 이용하는 데 맛들일지도 모르겠네요 :-)

이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by JEEN
사용자 삽입 이미지

 저도 슬슬 CPAN 모듈을 올려야하지 않을 까 하는 생각이 요즘 들어서 이래저래 찾아봐도 꺼리가 별로 보이지 않았습니다.

 Me2day API도 업로드 할 수 있지만.. 그대로 올리기는 왠지 좀 그렇고...
 Daum 이나 Naver API 도 있기는 하지만... 마땅히 하고 싶은 생각도 안들고...

 그래서 IRC 에서 amorette 님께서 한글 입력이 안된다는 이야기와 함께 LuzLuna 님께서 만들어라! 라는 이야기가 나와서 한번 손을 대 보았습니다.

 이른바 TDD DDD(DDuckbob-Driven Development) ... 떡밥주도개발 방식입니다.

 일단 CPAN에 이와 비슷한 모듈이 있는 가를 우선 확인해 보았습니다.

 Lingua::KO::(.+) 부분을 집중적으로 찾아봤지만.. 역시 없더군요.
 
 사실 예전에 PHPSCHOOL의 iamseeker 님께서 Java Script 도 구현해 놓은 게 있더군요. 그것을 참고로 해서 뚝딱 만들었습니다.

 CPAN 모듈을 만드는 것에 대한 한글 문서는 어디를 찾아봐도 나오지 않으니 perldoc(perlnewpod) 도 이번기회에 번역해버렸구요(perldoc-kr 플젝).

 뭐 대강 준비를 마쳤습니다.

 1. 모듈을 만들기 위한 스켈레톤을 생성합니다.

  : 이것은 h2xs 로 가능한데요. 사실 h2xs 는 C Header File 을 XS 모듈로 하기 위한 유틸리티인데요 Perl 모듈용 스켈레톤으로 쓰인다고 하네요.

사용자 삽입 이미지

 -A 옵션은 Autoloader 를 생략하고, -X 옵션은 XS를 생략합니다. 어제 shibuya.pm tech talk 에서 XS 에 대한 공포감이 있기도 했고... 사실 XS 를 사용할 필요가 없는 100% Perl Code 만으로 만들테니까요. 그리고 -n 옵션으로 모듈이름을 넣어줍니다.
 아.. -b 옵션은 이 모듈을 돌리기 위한 Perl Version 을 제한합니다. 그러니 이 때는 5.8.8 이상에서만 돌릴 수 있다고 제한하는 겁니다. 일단 이름은 MyTest::Module 로 지정했습니다.

사용자 삽입 이미지

 MyTest-Module 이라는 디렉토리에 가면 이와 같은 파일/디렉토리 구성이 보입니다.

 Changes 는 이 모듈의 해당 버젼에서 어떤 것이 바뀌었나 하는 Change Log 같은 것을 기술합니다.
 README 는 모듈 사용에 관한 당부나 주의사항, 설치방법등을 기술하구요.
 MANIFEST 는 배포할 파일들을 지정해 줍니다.
 Makefile.PL 을 이용해서는 Makefile 을 만들고. make test 를 통해서 테스트를 make install 을 통해서 설치를 할 수 있으며, make dist 를 통해서 배포파일을 지정해줄 수 있습니다.

 t/ 의 아래에는 Test 코드들을 놓구요.
 lib/ 아래에는 모듈을 구성하는 pm 파일들이 놓여집니다. 여기가 바로 코드를 넣을 부분이죠.

사용자 삽입 이미지

 lib/MyTest/Module.pm 을 열면 이와같은 코드들이 뼈대를 구성하고 있습니다.
 아까전에 -b 옵션으로 정해준 Perl version 에 관한 제약은 여기에 들어갑니다. (use perl 5.008008)
 그리고 기본적으로 Exporter 를 통해서 필요한 subroutine 들을 export 할 수도 있습니다.
사용자 삽입 이미지

 그리고 그 아래에는 이 모듈에 대한 설명이나 AUTHOR 정보, 라이센스 정보를 담는 POD 가 있습니다. 만약 POD에 대해 모르신다면 perldoc(perlpod) 를 참조하시면 됩니다(perldoc-kr, doc.perl.kr 에서 찾으실 수 있습니다).

  일단 코드는 간단하게 이렇게 넣어봤습니다.

our @EXPORT = qw(blah);

sub blah {
  length(shift);
}

  blah 에 적당한 인수를 넣어 호출하면 길이를 반환하는 예제입니다. 물론 아무짝에도 쓸모없습니다.
  그리고 @EXPORT 할 것에 blah 를 추가해줍니다.

사용자 삽입 이미지

  'perl Makefile.PL' 을 하면...
사용자 삽입 이미지
  Makefile 이라는 파일이 생기게 되구요.
  그리고 모듈의 test 를 실행합니다. 테스트 코드는 t/MyTest-Module.t 파일에 기본적으로 쓰여진 코드입니다.

사용자 삽입 이미지
  그리고 make install 을 통해서 Perl 모듈의 기본 저장소에 이 모듈이 설치됩니다.
  그러면 어디서든지 'use MyTest::Module' 을 통해서 이 모듈을 사용할 수 있게 됩니다.

사용자 삽입 이미지
  그리고 'make dist' 를 통해서 CPAN 업로드 등의 배포를 위한 tarball 파일이 만들어지게 됩니다. 이 파일을 CPAN이나 아니면 다른 사람에게 배포할 때 쓰면 되겠죠? (물론 SVN 같은 것을 사용하는 것이 요즘 추세이기는 합니다만...)
 
사용자 삽입 이미지
  그래서 이처럼 MyTest::Module 모듈을 부르고 blah 를 호출해서 결괄르 얻었습니다. 결과는 12가 나오네요.

 이런식으로 간단하게 CPAN 모듈을 만들고, 테스트하고, 배포할 수 있습니다.
어때요? 참 쉽죠?

 그렇지만 제대로  된 테스트 코드도 쓰지 않았고, h2xs 로 생성되는 스켈레톤은 기본적으로 낡은 방식의 Makefile.PL을 사용하고 있습니다(use ExtUtils::MakeMaker).

  이 부분에 대해서는 다음에 천천히 설명하도록 하겠습니다. :-)
이올린에 북마크하기(0) 이올린에 추천하기(0)

'IT > Perl' 카테고리의 다른 글

[ Perl ] CPAN 모듈 만들기!! #2  (0) 2008/07/04
[ Perl ] CPAN 모듈 만들기!! #1  (0) 2008/06/26
[ Perl ] Shibuya.pm Tech Talk #9 XS Nite 갔다왔습니다.  (0) 2008/06/25
[ Perl ] Test 이야기 #1  (0) 2008/06/24
Posted by JEEN