Meadows of wild horses

Blog...

List-edit

| Comments

청구 내역 수정

List-edit-action

수정 되어야할 $edit_id를 받을수 있어햐 한다.

sub edit :Local :CaptureArgs(1) {
my ( $self, $c, $edit_id ) = @_;

수정할 폼을 제출 했을때 업데이트 동작 혹은 정보 출력을 하여야 한다.

if ($c->req->method eq 'POST') {
    my @messages;

    push @messages, 'amount is invaild' if ($c->req->params->{amount} !~ /^\d+$/);
    push @messages, 'title is required' unless ($c->req->params->{title});

    if (@messages) {
    $c->flash(
        messages => @messages,
        comment  => $c->req->params->{comment},
        title    => $c->req->params->{title},
        amount   => $c->req->params->{amount},
    );

    return $c->res->redirect($c->uri_for("/list/view/$c->req->params->{charge_id}"));
    }

    my $time = strftime "%Y-%m-%d %H:%M:%S", localtime;
    my %row = (
    id         => $c->req->params->{charge_id},
    amount     => $c->req->params->{amount},
    user       => $c->req->params->{charge_user},
    title      => $c->req->params->{title},
    comment    => $c->req->params->{comment},
    updated_on => "$time",
    );
    $c->model('DonDB')->resultset('Charge')->update_or_create(\%row);

    $c->res->redirect($c->uri_for("/list/view/$row{id}"));
}
else {
    my $editer = $c->model('DonDB')->resultset('Charge')->find($edit_id);

    $c->stash(
    editer => $editer,
    );
}
}

List-edit.tt

List

| Comments

청구 목록 페이지

List.pm - index

/list/index.tt

List Controller - Query option

등록 날짜 역순 출력을 위한 내림 차수 옵션을 준다.

my %attr  = ( 'order_by' => { -desc => 'me.id' } );

my $total_charge;
my %cond    = ();

현재 가르켜야 할 페이지 정보가 없다면 1페이지로 하게 한다.

my $page    = $c->req->params->{page};
$attr{page} = $page || 1;

status 옵션이 정의 되지 않았다면 전체(대기/승인/거부) 검색을 할수 있게 한다.

my $status  = $c->req->param("status") || $c->stash->{"status"} || '0'; #수정 필요

status 옵션 없으면 전체 있으면 상태 검색 할수 있게 한다.

%cond         = ( status => $status ) if $status;
$total_charge = $c->model('DonDB')->resultset('Charge')->search(\%cond, \%attr);

Data::Pageset활용 하여 페이징 할수 있다.

my $page_info =
Data::Pageset->new(
    {
    ( map { $_ => $total_charge->pager->$_ } qw/entries_per_page total_entries current_page/ ),
    mode => "slide",
    pages_per_set => 10,
    }
);

$c->stash(
    lists   => [ $total_charge->all ],
    status  => $status,
    pageset => $page_info,
);
}

List - View

성공 혹은 실패 메세지 출력을 할수 있게 한다.

[% IF messages %]
<div class="alert fade in">
<button class="close" data-dismiss="alert">&times;</button>
[% messages %]
</div>
[% END %]

탭 등록으로 원하는 상태의 값들만 보여 줄수 있게 한다.

<div class="container-fluid">
  <div class="row-fluid">
<div class="span2">
  <ul class="nav nav-tabs nav-stacked">
    <li class="active"><a href="/list">전체</a></li>
    <li><a href="/list?status=1">대기</a></li>
    <li><a href="/list?status=2">승인</a></li>
    <li><a href="/list?status=3">거부</a></li>
  </ul>
</div>

현재 경로가 어떻게 되는지 확인 할수 있도록 한다.

<div class="span10">
  <ul class="breadcrumb">
    <li>목록
        <span class="divider">/</span>
    </li>      
    <li class="active">
    [% IF status == "1" %]
        <b> 대기 </b>
    [% ELSIF status == "2" %]
        <b> 승인 </b>
    [% ELSIF status == "3" %]
        <b> 거부 </b>
    [% ELSE %]
        <b> 전체 </b>
    [% END %]     
    </li>
  </ul>

CPAN

Data::Pageset

DBIx::Class::Manual::DocMap

Catalyst-signup

| Comments

Signup Controller Login.pm

$ vi lib/MyApp/Controller/Login.pm
# 내용 추가

:Private

forword 혹은 detach로만 접근이 가능 하며, URL로 부터 바로 접근이 불가능 합니다.

sub signup :Path('/signup') :Args(0) {
my ( $self, $c ) = @_;

$c->detach('signup_POST') if $c->req->method eq 'POST';
}

sub signup_POST :Private {
my ( $self, $c ) = @_;

Validation - Empty Value

입력값중에 빈값이 있으면 안돼므로 빈값인지 확인 하여 빈값일시 메세지를 출력 할수 있게 작업한다.

my $user_name = $c->req->param('user_name') || '';
my $email     = $c->req->param('email')     || '';
my $password  = $c->req->param('password')  || '';

my @messages;
push @messages, 'Input the user name'     unless $user_name;
push @messages, 'Input the user email'    unless $email;
push @messages, 'Input the user password' unless $password;

if (@messages) {
    $c->flash(
        messages => @messages,
    );

    return $c->res->redirect($c->uri_for('/signup'));
}

Validation - DB Unique search

User table에 user_name과 email이 동일한 값이 있는지 확인 하여 동일한 값이 있을시 error_msg를 출력 할수 있게 한다.

my $cond = {};
$cond->{'me.user_name'} = "$user_name";
my $name_search = $c->model('DonDB')->resultset('User')->search($cond);
if ($name_search->count) {
    $c->flash(
        messages => 'Using ID again input the New ID',
    );

    return $c->res->redirect($c->uri_for('/signup'));
}

$cond = {} if $cond;
$cond->{'me.email'} = "$email";
my $email_search = $c->model('DonDB')->resultset('User')->search($cond);
if ($email_search->count) {
    $c->flash(
        messages      => 'Using email again input the New email',
    );

    return $c->res->redirect($c->uri_for('/signup'));
}

유저 등록

Validation이 통과 했다면 테이블에 등록 할수 있게 한다. 실패시 signup페이지로 다시 이동 하며

my $time    = strftime "%Y-%m-%d %H:%M:%S", localtime;
my $created = $c->model('DonDB::User')->create({
    user_name  => $user_name,
    email      => $email,
    password   => $password,
    created_on => "$time",
    updated_on => "$time",
});

$c->res->redirect($c->req->uri) unless $created;

유저 등록 - 확인

등록후 이름과 패스워드를 확인하여 다음 페이지 혹은 tdpfjaptpwldhk 냐혀ㅔvpdlwlfmf ghcnfgksek.

if ($c->authenticate({ user_name => $user_name, password => $password } )) {
    $c->res->redirect($c->uri_for($c->controller('List')->action_for('index')));
} else {
    $c->stash(error_msg => "Bad username or password."); # maybe flash?
    $c->res->redirect($c->req->uri);
}

}

Signup View.tt signup.tt

$ vi root/templates/default/src/login/signup.tt
# 내용 추가

jquery 미사용

현재 Validation은 컨트롤 단에서만 검사 한다 추가가 필요함

Catalyst-Login

| Comments

Catalyst::Manual::Tutorial::05_Authentication

Load plugins

/lib/MyApp.pm

$ vi ./lib/MyApp/Web/MyApp.pm

# 아래에 다음과 같이 추가
use Catalyst qw/
-Debug
ConfigLoader
Static::Simple
Unicode::Encoding

StackTrace

Authentication

Session
Session::Store::File
Session::State::Cookie
/;

MakeFile.PL

$ vi Makefile.PL

# 추가
requires 'Catalyst::Plugin::Authentication';
requires 'Catalyst::Plugin::Session';
requires 'Catalyst::Plugin::Session::Store::File';
requires 'Catalyst::Plugin::Session::State::Cookie';

$ carton install

MyApp.conf

$vi MyApp.conf

# 추가
<Plugin::Authentication>
<default>
    password_type clear
    user_model    DB::User
    class         SimpleDB
</default>
</Plugin::Authentication>

Add Login and Logout Controllers

Controller 생성

$ script/myapp_create.pl controller Login
$ script/myapp_create.pl controller Logout

Login Login.pm

$ vi lib/MyApp/Controller/Login.pm

Logout Logout.pm

$ vi lib/MyApp/Controller/Logout.pm

Add Valid User Check

Root Root.pm

$ vi lib/MyApp/Controller/Root.pm

추가 해야 할점

현재는 DB에 유저의 password가 암호화 되지 않음 문서상 다음 내용에 나오기 때문에 추가가 필요함

CPAN

DBIx::Class::Manual::DocMap

Catalyst

| Comments

DBIx::Class로 스키마 관리하기

jeen님이 발표한 스키마 관리 하기 입니다.

jeen님의 글을 보게 되면 기존의 스키마를 관리하는 방법은 sql의 잦은 교체시 많은 문제점이 발생하고 있다고 서술 합니다.

스키마 관리를 좀더 편하게 하기 위하여 마지막에 서술 하는 dbicdump를 사용하여 간편하게 스키마 관리를 해보도록 합니다.

dbicdump를 사용하기 위해 필요한 ResultBase.pm과 schema.pl파일을 만들어 보도록 하겠습니다.

schema/schema.pl

schema.pl 설명

schema.pl
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
my $DB_NAME     = $ENV{DB}           || 'DB_NAME';
my $DB_USER     = $ENV{DB_USER}     || 'DB_USER';
my $DB_PASSWORD = $ENV{DB_PASSWORD} || 'DB_PASSWORD';

{
schema_class => "Silex::Schema",
connect_info => {
  dsn               => "dbi:mysql:$DB_NAME:127.0.0.1",
  user              => $DB_USER,
  pass              => $DB_PASSWORD,
  mysql_enable_utf8  => 1,
},
loader_options => {
  dump_directory     => 'lib',
  naming             => { ALL => 'v8' },
  skip_load_external => 1,
  relationships      => 1,
  use_moose          => 1,
  only_autoclean     => 1,
  col_collision_map  => 'column_%s',
  result_base_class => 'MyApp::Schema::ResultBase',
  overwrite_modifications => 1,
  datetime_undef_if_invalid => 1,
  custom_column_info => sub {
  my ($table, $col_name, $col_info) = @_;

  if ($col_name eq 'password') {
      return { %{ $col_info },
          encode_column => 1,
          encode_class  => 'Digest',
          encode_args   => { algorithm => 'SHA-1', format => 'hex' },
          encode_check_method => 'check_password' };
  } # DB password 설정
  if ($col_name eq 'created_on') {
      return { %{ $col_info }, set_on_create => 1, inflate_datetime => 1 };
  } # DB created_on 설정

  if ($col_name eq 'updated_on') {
      return { %{ $col_info }, set_on_create => 1, set_on_update => 1, inflate_datetime => 1 };
  } # DB updated_on 설정
  },
  },
}

lib/MyApp/Schema/ResultBase.pm

ResultBase 클래스의 제약 문제 때문에 result_base_class 값을 지정해 줌으로써 모든 결과 클래스들은 DBIx::Class::Core가 아닌 MyApp::Schema::ResultBase를 상속받게 합니다. MyApp::Schema::ResultBase는 개인이 정의 해주는것 이므로 아래를 참조 합니다.

MD5 체크섬 값 아래에 중복되는 코드를 매번 적어주어야 했습니다. 이렇게 사용할 컴포넌트들을 결과클래스 별로 지정하는 대신 ResultBase 클래스를 읽어들입니다. 사실 이처럼 ResultBase를 놓고 여기에 컴포넌트를 일괄해서 읽어들이는 방식은 Cookbook 문서에서도 스타트업 속도 향상을 위해 권장하고 있습니다. - by Jeen -

그리고 컴포넌트의 사용을 위한 컬럼의 속성은 custom_column_info 속성에 코드를 등록해 지정할 수 있습니다.(schema.pl 하단 참조) 위의 코드처럼 등록하면 created_on 속성에 TimeStamp 컴포넌트를 사용하기 위한 속성 값인 set_on_create 옵션이 모든 결과클래스에 추가됩니다. (이거 멋지다..)

ResultBase
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package MyApp::Schema::ResultBase;
use Moose;
use MooseX::NonMoose;
use namespace::autoclean;

extends 'DBIx::Class::Core';

__PACKAGE__->load_components(qw/
  InflateColumn::DateTime
  TimeStamp
/);

__PACKAGE__->meta->make_immutable;

1;

위의 두가지 코드를 작성 하였다면 준비는 끝났습니다. (dbicdump는 db 정보를 덤프해서 스키마를 작성하는 것으로 코드 작성및 작성 후에라도 db가 정의 되어 있어야 합니다.)

dbicdump 사용하기

아래를 실행한후 lib/MyApp/Schema/Result에 .pm 파일들이 새로 생성되거나 수정 되었다면 확인을 해보라

$ dbicdump -Ilib schema/schema.pl

MakeFile.pl 모듈 추가

다른 모듈이 더 추가 될수 있습니다 에러 메세지를 확인 하고 필요한 모듈을 더 설치 하세요.

requires "DBIx::Class::TimeStamp";

Catalyst

| Comments

DB confing 셋팅 정보

my $DB_NAME     = $ENV{DB_NAME}     || 'NAME';
my $DB_USER     = $ENV{DB_USER}     || 'USER';
my $DB_PASSWORD = $ENV{DB_PASSWORD} || 'PASSWORD';

옵션 내용

DBIx::Class::Schema::Loader::Base

schema_class => name of the schema class we are building,

적용

schema_class => "Project::Schema",

  • dump_directory

The value of this option is a perl libdir pathname. Within that directory this module will create a baseline manual DBIx::Class::Schema module set, based on what it creates at runtime.

The created schema class will have the same classname as the one on which you are setting this option (and the ResultSource classes will be based on this name as well).

Normally you wouldn’t hard-code this setting in your schema class, as it is meant for one-time manual usage.

See “dump_to_dir” in DBIx::Class::Schema::Loader for examples of the recommended way to access this functionality.

적용

dump_directory => 'lib',

naming

Static schemas (ones dumped to disk) will, by default, use the new-style relationship names

and singularized Results, unless you’re overwriting an existing dump made by an older version

of DBIx::Class::Schema::Loader, in which case the backward compatible RelBuilder will be activated,

and the appropriate monikerization used.

Specifying

naming => 'current'

will disable the backward-compatible RelBuilder and use the new-style relationship names along with singularized Results, even when overwriting a dump made with an earlier version.

The option also takes a hashref:

naming => {
    relationships    => 'v8',
    monikers         => 'v8',
    column_accessors => 'v8',
    force_ascii      => 1,
}

or

naming => { ALL => 'v8', force_ascii => 1 }

The keys are:

ALL

Set “relationships”, “monikers” and “column_accessors” to the specified value.

relationships

How to name relationship accessors.

monikers

How to name Result classes.

column_accessors

How to name column accessors in Result classes.

force_ascii

For “v8” mode and later, uses String::ToIdentifier::EN instead of String::ToIdentifier::EM::Unicode to force monikers and other identifiers to ASCII.

The values can be:

current

Latest style, whatever that happens to be.

v4

Unsingularlized monikers, has_many only relationships with no _id stripping.

v5

Monikers singularized as whole words, might_have relationships for FKs on UNIQUE constraints, _id stripping for belongs_to relationships.

Some of the _id stripping edge cases in 0.05003 have been reverted for the v5 RelBuilder.

v6

All monikers and relationships are inflected using Lingua::EN::Inflect::Phrase, and there is more aggressive _id stripping from relationship names.

In general, there is very little difference between v5 and v6 schemas.

v7

This mode is identical to v6 mode, except that monikerization of CamelCase table names is also done better (but best in v8.)

CamelCase column names in case-preserving mode will also be handled better for relationship name inflection (but best in v8.) See “preserve_case”.

In this mode, CamelCase “column_accessors” are normalized based on case transition instead of just being lowercased, so FooId becomes foo_id.

v8

(EXPERIMENTAL)

The default mode is “v7”, to get “v8” mode, you have to specify it in “naming” explicitly until 0.08 comes out.

“monikers” and “column_accessors” are created using String::ToIdentifier::EN::Unicode or String::ToIdentifier::EN if “force_ascii” is set; this is only significant for names with non-\w characters such as ..

CamelCase identifiers with words in all caps, e.g. VLANValidID are supported correctly in this mode.

For relationships, belongs_to accessors are made from column names by stripping postfixes other than _id as well, for example just Id, _?ref, _?cd, _?code and _?num, case insensitively.

적용

naming => { ALL => 'v8' },

relationships

How to name relationship accessors.

적용

relationships => 1,

# 왜 여기만 1이지? ALL => v8이었는데?

skip_load_external

Skip loading of other classes in @INC. The default is to merge all other classes with the same name found in @INC into the schema file we are creating.

적용

skip\_load_external => 1,

use_moose

Creates Schema and Result classes that use Moose, MooseX::NonMoose and MooseX::MarkAsMethods (or namespace::autoclean, see below). The default content after the md5 sum also makes the classes immutable.

It is safe to upgrade your existing Schema to this option.

적용

use_moose          => 1,

only_autoclean

By default, we use MooseX::MarkAsMethods to remove imported functions from your generated classes. It uses namespace::autoclean to do this, after telling your object’s metaclass that any operator overloads in your class are methods, which will cause namespace::autoclean to spare them from removal.

This prevents the “Hey, where’d my overloads go?!” effect.

If you don’t care about operator overloads, enabling this option falls back to just using namespace::autoclean itself.

If none of the above made any sense, or you don’t have some pressing need to only use namespace::autoclean, leaving this set to the default is recommended.

적용

only_autoclean     => 1,

col_collision_map

This option controls how accessors for column names which collide with perl methods are named. See “COLUMN ACCESSOR COLLISIONS” for more information.

This option takes either a single sprintf format or a hashref of strings which are compiled to regular expressions that map to sprintf formats.

Examples:

col_collision_map => 'column_%s'

col_collision_map => { '(.*)' => 'column_%s' }

col_collision_map => { '(foo).*(bar)' => 'column_%s_%s' }

적용

col_collision_map  => 'column_%s',

result_base_class

Base class for your table classes (aka result classes). Defaults to ‘DBIx::Class::Core’.

적용

result_base_class => 'Evid::Schema::ResultBase',

overwrite_modifications

Default false. If false, when updating existing files, Loader will refuse to modify any Loader-generated code that has been modified since its last run (as determined by the checksum Loader put in its comment lines).

If true, Loader will discard any manual modifications that have been made to Loader-generated code.

Again, you should be using version control on your schema classes. Be careful with this option.

적용

overwrite_modifications => 1,

datetime_undef_if_invalid

Pass a 0 for this option when using MySQL if you DON’T want datetime_undef_if_invalid => 1 in your column info for DATE, DATETIME and TIMESTAMP columns.

The default is recommended to deal with data such as 00/00/00 which sometimes ends up in such columns in MySQL.

적용

datetime_undef_if_invalid => 1,

custom_column_info

Hook for adding extra attributes to the column_info for a column.

Must be a coderef that returns a hashref with the extra attributes.

Receives the table object (which stringifies to the unqualified table name), column name and column_info.

For example:

custom_column_info => sub {
my ($table, $column_name, $column_info) = @_;

if ($column_name eq 'dog' && $column_info->{default_value} eq 'snoopy') {
    return { is_snoopy => 1 };
}
},

적용

{
loader_options => {
    custom_column_info => sub {
    my ($table, $col_name, $col_info) = @_;

    if ($col_name eq 'password') {
        return { %{ $col_info },
            encode_column => 1,
            encode_class  => 'Digest',
            encode_args   => { algorithm => 'SHA-1', format => 'hex' },
            encode_check_method => 'check_password' };
    }

    if ($col_name eq 'created_at') {
        return { %{ $col_info }, set_on_create => 1, inflate_datetime => 1 };
    }

    if ($col_name eq 'update_at') {
        return { %{ $col_info }, set_on_create => 1, set_on_update => 1, inflate_datetime => 1 };
    }

    if ($col_name eq 'data') {
        return { %{ $col_info }, serializer_class => 'JSON' };
    }
    },
},
}

cpan dbicdump

connect_info => {
dsn               => "dbi:mysql:$DB_NAME:127.0.0.1",
user              => $DB_USER,
pass              => $DB_PASSWORD,
mysql_enable_utf8  => 1,
},

Catalyst

| Comments

Error message

기존에 사용되던 myweb.conf 파일의 ERROR 500.tt 메세지로 인해 에러 메세지가 제대로 보이지 않아 삭제 했습니다.

$ vi myweb.conf
ERROR 500.tt 삭제

Makefile.PL 모듈 추가

$ vi Makefile.PL
require DateTime::Format::MYSQL

DB 날짜 입력

lib/Silex/Web/Donnenwa/Controller/List.pm

등록시 날짜 생성을 위해 POSIX모듈을 사용합니다. 현재시간에

형식을 DB와 같이 “%Y-%m-%d% %H:%M:%S” 만들어 입력합니다.

sub write :Local :Args(0) {
...
use POSIX qw(strftime);

my $time = strftime "%Y-%m-%d% %H:%M:%S", localtime;
my %row = (
applicant  => $c->req->params->{applicant},
title      => $c->req->params->{title},
content    => $c->req->params->{content},
created_on => "$time",
updated_on => "$time",
);
...

list 페이지 쿼리문 수정

lib/Silex/Web/Donnenwa/Controller/List.pm

생성될 날짜순으로 출력하기 위한 쿼리문 작성 search의 첫번째 인자는 where문으로써 무시 하기 위해서는 빈 해쉬레퍼를 줍니다.

sub index :Path :Args(0) {
...
my $opt  = { 'order_by' => { -desc => 'me.created_on' } };
my $rs = $c->model('DonDB')->resultset('Charge')->search({}, $opt);
...

list.tt, list/view.tt 페이지 수정

root/templates/bootstrap/src/list/index.tt root/templates/bootstrap/src/list/view.tt

날짜 0000-00-00 형식으로 출력 지정

<td align=center height=20 bgcolor=white>[% list.created_on.ymd %]

기타

날짜 관련 예제

#!/usr/bin/env perl 

use 5.010;
use strict;
use warnings;
use POSIX qw(strftime);
use DateTime;

say "POSOX";
say POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime);

say "DateTime";
say DateTime->now( time_zone => 'local' )->strftime('%Y-%m-%d %H:%M:%S');

Catalyst

| Comments

/list/write.tt 페이지

write.tt

form action

<form action="[% c.uri_for('/list/write') %]" method="POST">

direction setting

<td colspan=10 align=center>
  <input type=submit class="btn btn-primary" value="글 저장하기">
  &nbsp;&nbsp;
  <input type=reset class="btn btn-primary" value="다시 쓰기">
  &nbsp;&nbsp;
  <a href="[% c.uri_for('/list') %]" class="btn btn-primary">되돌아가기</a>
</td>

list -> write

$ lib/Silex/Web/Donnenwa/Controller/List.pm

sub write :Local :Args(0) {
my ( $self, $c ) = @_;

if ($c->req->method eq 'POST') {
    my %row = (
    name    => $c->req->params->{name},
    title   => $c->req->params->{title},
    content => $c->req->params->{content},
    );
    $c->model('DonDB')->resultset('List')->update_or_create(\%row);
    $c->res->redirect($c->uri_for('/list'));
}
}

/list/view.tt 페이지

view.tt

인자 출력

// 제목
<tr>
  <td height=20 colspan=4 align=center bgcolor=#999999>
<font color=white><b>[% users.title %]</b></font>
  </td>
</tr>

// 이름
<tr>
  <td width=50 height=20 align=center bgcolor=#EEEEEE>글쓴이</td>
  <td width=240 bgcolor=white>[% users.name %]</td>
</tr>

// 내용
<tr>
  <td bgcolor=white colspan=4 style="table-layout:fixed;">
<font color=black>
  [% users.content %]
</font>
  </td>
</tr>

list page redirect

<a href="[% c.uri_for('/list') %]" class="btn btn-primary">되돌아가기</a>

list -> view

$ lib/Silex/Web/Donnenwa/Controller/List.pm

sub view :Local :CaptureArgs(1) {
my ( $self, $c, $user_id) = @_;

my $rs = $c->model('DonDB')->resultset('List')->find($user_id);
$c->stash->{users} = $rs;
}