Silex Blog

What the silex.

IRC Bot

| Comments

IRC 에는 많은 봇들이 살고 있습니다.

봇은 일반적으로 regex rule과 callback을 등록해두고 해당하는 대화 내용이 나오면 callback 이 실행되는 구조를 가집니다.

여기서는 두개의 오픈소스 봇을 소개하고 어떻게 deploy 할 것인지에 대해 다룹니다.

Hongbot

AnyEvent 모듈 기반이고 perl로 만들어졌습니다.

Morris -> Horris -> Hongbot 의 계보를 가지고 있습니다. plugin 파일을 만들어서 확장할 수 있습니다.

plugins

  • Ascii
  • Eval
  • Hello
  • Mac
  • Map
  • MetaCPAN
  • Mustache
  • Shorten
  • Twitter
  • Youtube

run

1
2
3
4
5
$ git clone git@github.com:aanoaa/Hongbot.git
$ cd Hongbot/
$ carton install # Carton 이 설치되어 잇어야..
$ vim conf/hongbot.conf # 서버, 채널, password 등을 변경해야..
$ ./run

hubot

Github에서 공개한 CoffeeScript 로 쓰여진 node.js 기반의 bot 입니다. core인 hubot 이 있고 hubot-scripts 를 통해 확장 가능합니다. adapter 를 설정해서 irc 뿐만아니라 campfire, gtalk 에서도 사용가능 합니다. 물론 adapter 또한 확장 가능 합니다.

scripts

hubot-scripts catalog

지속가능한 운영의 묘

hubot의 README 를 보면 tarball 을 Download 받아서 하라고 하는데, 그렇게 하면 npm을 통해서 hubot-scripts 가 설치되기 때문에 자신의 확장 scripts 를 사용할 수 없습니다.

해서,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ git clone https://github.com/github/hubot.git
$ git clone https://github.com/github/hubot-scripts.git
$ cd hubot-scripts/
$ npm install # npm 이 설치되어 잇어야 합니다.
$ cd ../
$ cd hubot/
$ mkdir /path/to/deploy/hubot
$ ./bin/hubot -c /path/to/deploy/hubot
$ cd /path/to/deploy/hubot
$ npm install
$ cd node_modules
$ rm -rf hubot-scripts/ # npm의 hubot-scripts 대신에 clone 받은 hubot-scripts 를 사용합니다.
$ ln -s /path/to/hubot-scripts hubot-scripts # 아까 clone 받은 repo
$ cd ../
$ vim hubot-scripts.json
    # /path/to/hubot-scripts/src/scripts/*.coffee 사용가능
    # ["foo.coffee", "bar.coffee"] 등등..
$ ./bin/hubot

hubot-scripts를 fork 해서 사용하는게 유리합니다. github:hubot-scripts 를 remote 로 등록해서 꾸준히 추가되는 script를 쓸수도 있고 내가 작성한 script도 쓸 수 있으니까여

plugin, script 의 단순함 비교

hongbot Eval.pm

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
package Hongbot::Plugin::Eval;
use URI;
use JSON;
use Moose;
use AnyEvent::HTTP;
use namespace::autoclean;
extends 'Hongbot::Plugin';
with 'MooseX::Role::Pluggable::Plugin';

has uri => (
    is => 'ro',
    isa => 'URI',
    default => sub { URI->new("http://api.dan.co.jp/lleval.cgi") },
);

has prefix => (is => 'ro', isa => 'Str', default => "#!/usr/bin/perl\n");

override 'usage' => sub { sprintf("%s: eval <PERL_CODE>", $_[0]->parent->name) };
override 'regex' => sub { qr/^eval\s+/i };

sub respond {
    my ($self, $cl, $channel, $nickname, $msg) = @_;

    $msg = $self->rm_prefix($self->regex, $msg);
    return unless $msg;

    $msg = $self->prefix . $msg;
    $self->uri->query_form(s => $msg);
    my $guard; $guard = http_get $self->uri, sub {
        undef $guard;
        my ($body, $headers) = @_;
        if ($headers->{Status} =~ m/^2/) {
            my $scalar = JSON::from_json($body);
            my @eval;
            map { push @eval, "$_: $scalar->{$_}" } qw/lang status stderr stdout syscalls time/;
            $self->to_channel($cl, $channel, @eval);
        } else {
            $self->to_channel($cl, $channel, sprintf("httpCode: %d", $headers->{Status}));
        }
    };
}

__PACKAGE__->meta->make_immutable;

1;

hubot eval.coffee

1
2
3
4
5
6
7
8
9
10
11
12
# evaluate code.
#
# eval me <lang> <code> - evaluate <code> and show the result.

module.exports = (robot) ->
  robot.respond /eval( me)? ([^ ]+) (.+)/i, (msg) ->
    msg
      .http("http://api.dan.co.jp/lleval.cgi")
      .query(s: "#!/usr/bin/#{msg.match[2]}\n#{msg.match[3]}")
      .get() (err, res, body) ->
        out = JSON.parse(body)
        msg.send if out.stderr then out.stderr else out.stdout

hubot 이 훨씬 단순합니다. core 의 설계가 더 유연하고 잘되어 있어서 그렇다고 생각합니다. Hongbot은 저혼자 하고 hubot은 세계의 여러 해커들이 참여하는 프로젝트니까 뭐 비교하면 저만 초라해집니다. Hongbot의 core 를 업그레이드 시켜서 hubot 만큼 단순하게 plugin 을 작성할 수 있게 하면 좋은 plugin 들이 많이 생겨날 것으로 생각합니다. repo 와 deploy 디렉토리를 따로 둘수 있게 만든 점 또한 hubot++ 입니다. 배울게 참 많습니다.

결론

여보 아버님채널에 bot 한마리 놔드려야 겠어요

See also

Guest Speech #1 - G사 S님

| Comments

지난 13일 CEO @y0gnbin 의 지인인 G 사 S모님을 초청해서 서버 가상화에 대한 이런 저런 얘기를 들을 수 있었습니다.

실제로 CentOS 5.7 에 기본으로 세팅된 환경 아래에서 실제로 Dom-U 를 만드는 과정과 주의점 등등에 대한 이야기가 주를 이뤘습니다.

물론 그 뒤에는 단순히 Xen 환경 구축에 관한 이야기 뿐만 아니라, G 사에 Xen 을 도입한 이야기, 그리고 G 사에서 오픈소스를 본격적으로 채용함으로 얻은 이런저런 메리트. 그리고 작은 회사에서는 감히 엄두도 못 낼 장비 스펙과 비용, 많은 성공사례와 오픈소스 도입에 이르기까지의 계측된 데이터와 검증 방법…

또한 서버구입시에 참고할 만한 스펙비교 방법도 전수받게 되었습니다.

아무튼 이런저런 돈주고도 쉽게 못 들을 이런저런 얘기는 장소를 바꿔가며 새벽 2시까지 이어갔고, 그때가 되어서야 해산을 했습니다.

backbone.js

| Comments

배경

backbone.js 가 만들어진 배경이 아니라, 제가 사용하게 된 배경입니다. 회사에서 진행하는 javascript 를 나름 무겁게 사용하는 프로젝트가 있습니다. “갑”님의 요구사항이 날이 갈수록 복잡해 짐에 따라 jquery 플러그인도 맨들고, require.js 를 사용해서 파일을 조직화해서 사용하고 있었습니다만 $.ajax 에 대한 callback 이 늘어남에 따라 점점 코드의 복잡도가 증가해서 해결해야 될 문제라고 생각했습니다.

Trello 에서 The Trello Tech Stack backbone.js를 쓴다는 글을 보고 사용방법을 좀 알아보았습니다.

Trello Early Architecture Drawing

자세한 설명은 (몰라서)생략

왜 쓰는지 뭔가 좋은지 그런거 모릅니다. 여기서는 어떻게 사용하는지만 다룹니다. 검색 ㄱㄱ

자세한 설명은 생략한다

맨든사람

Jeremy Ashkenas - Twitter, Jeremy Ashkenas - Github

요즘 가장 인기있는 코딩왕이 아닐까 합니다.

위에 세개다 저 사람이 맨들었답니다.

돈워리

http://documentcloud.github.com/backbone/#examples

  • FourSuare
  • LinkedIn
  • 37 Signals
  • Trello
  • Silex(끼워 넣어 봅니다)

등에서 이미 쓰이고 있다고..

연습삼아 맨든 프로젝트

일단 CoffeeScript만세라서 CoffeeScript로 구현하였습니다.

jquery-note plugin을 다시 한번 만들어 봤습니다.

Server-side

server-side

RESTful 하게 구현되어야 합니다. backbone.jsmodelcollection은 url 규약을 가지고 있습니다.

1
2
3
4
5
6
7
# http://documentcloud.github.com/backbone/#Sync
The default sync handler maps CRUD to REST like so:

create → POST   /collection
read → GET   /collection[/id]
update → PUT   /collection/id
delete → DELETE   /collection/id

좀 간단하게 말하면 위에선 언급한 구조의 HTTP method 와 url 규약을 지키고, 요청 헤더의 Accept에 걸맞는 응답을 줘야 합니다.

서버와 sync 를 하기 위해 model.save() 또는 collection.fetch()를 호출 하게 되는데 이때, 모든 요청 헤더에 Accept: application/json 을 포함합니다. 새로운 item 을 만들기 위해 POST 요청을 보낼때도 마찬가집니다. 이때는 추가로 JSON.stringify(model) 을 POST body 에 넣고, Content-type: application/json 헤더를 포함합니다.

1
2
3
4
# part of request header
Accept    application/json, text/javascript, */*; q=0.01
Content-Type  application/json; charset=UTF-8
X-Requested-With  XMLHttpRequest
1
2
# request body
{"qid":"1","status":"reopen","comment":""}

server 에서 이런 데이터를 받아서 처리해야 하는(deserialize/serialize) 자잘함을 피하기 위해 저는 Catalyst::Controller::REST 를 사용했습니다. 요청/응답 헤더에서 사용가능한 “Accept”를 확인하고 serialize/deserialize 를 자동으로 해주기 때문에 편리합니다.

Client-side

preview

  • 상태 및 코멘트를 note 라 하겠습니다.
  • 위 그림처럼 한화면에 보여지는 note의 집합(또는 컬렉션)을 query 라 하겠습니다.

http://example.com/#/query/:id url 을 감지하고 원하는 event 를 발생 시켜야 합니다. 그러기 위해선 Backbone.Router 를 확장해서 app 전용 router 를 만들어야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyModel extends Backbone.Model

class MyCollection extends Backbone.Collection
  model: MyModel

class MyRouter extends Backbone.Router
  routes:
    '/query/:id': 'query'
  query: (id) =>
    # do something
    coll = new MyCollection
    coll.url = "/query/#{id}"
    coll.fetch
      success: (c, res) ->
        new MyView { collection: c }
      error: (c, res) ->
        # error handling

http://example.com/#/query/2 일때, GET /query/2 하고 응답이 정상이면 MyView 를 만듭니다. view 가 초기화될때, render 를 호출하면 DOM 이 알맞게 슥슥 바뀌게 됩니다. 일반적으로 collection.models 를 순회하면서 html element 를 구성하고 특정 #id 에 append 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyView extends Backbone.View
  initialize: ->
    do @render
  template:
    '''
       is 
    '''
  render: =>
    _.each @collection.models, (item) =>
      # 템플릿을 사용해서 슥슥하면 편리합니다. 예를들어..
      html += Mustache.render(@template, { foo: 'hello', bar: 'world' }) # hello is world
      # 일반적으론 이렇게는 직접 데이터를 구성하지는 않겟고, item 을 사용하겟죠
      @delegateEvents() # event 처리에 대한 꼼순데 생략
      @ # render 의 마지막에 this 를 return 함으로써 jquery chain 을 사용할 수 있도록 합니다.

backbone.js 을 사용한 부분의 코드입니다. 조금 헷갈리수도 있는게, Require.js를 사용해서 define, order! 같은 구문이 거슬릴 수도 있습니다만, 바다와 같은 마음으로 이해해 주세요.

결론 및 느낀점

  • jquery plugin 으로 작성했을때 보다 복잡성은 줄어들고 코드의 양은 2/3 정도로 줄어듬
  • 확장이 용이 해졌음, 더 복잡한 UI 에 대한 자신감 up
  • js 가 웹애플리케이션의 일부분이 되어버림
  • jquery plugin 과 달리 재사용을 쉽게 못함
  • 서버 구현에 있어서 sync 를 위해서 RESTful하게 설계되어야함
  • rails 후렌들리
  • perl 로도 요런거 graceful 하게 할 수 있음
  • 요즘들어 ruby 에 많은 관심이 감
  • JST(Javascript Template)를 사용하면 more graceful, 허놔 그것은 rails only
  • template engine 바꿔 낄 수 있음

See also

Mysql ENUM and Test::*

| Comments

MySQL ENUM과 Test:: 이야기

MySQL의 ENUM type

제가 MySQL을 사용한지 약 10년 정도의 시간이 지났습니다. 처음 MySQL을 사용하던 당시 MySQL 3.X는 지금의 MySQL 5.X 버전에 비하면 DB로써 여러가지로 부족한 점이 많았습니다. - 단일 저장 engine 지원(MyISAM), 컴파일타임 전역 인코딩 지정, FK 지원, View/Trigger/SP 지원등 - 하지만 탁월한 읽기속도를 바탕으로 대중적인 몇몇 PHP 응용프로그램에 성공적으로 탑재되면서 거의 10년간 오픈소스 진영 DB의 대명사로 자리를 잡았고 그동안 많은 우여곡절에도 꾸준히 발전해 현재는 상용 DB와 견주어도 부족하지 않은 다양한 기능을 제공하고 있습니다.

하지만 개인적으로 그동안 MySQL에 추가된 좋은 최신의 기능들 보다 MySQL 이라는 DB를 떠올리면 가장 먼저 머리속에 떠오르는 애증의 기능이 하나 있습니다. 그것은 바로 오늘 소개할 ENUM이라는 자료형입니다.

ENUM은 C 언어, Java등에서 이미 널리 사용되고 있는 enumeration을 의미하는 자료형으로 MySQL에서 ENUM은 단일 컬럼에 미리 지정한 자료만 저장 가능하도록 하는 특수 자료형 입니다. MySQL.com 에서는 ENUM을 다음과 같이 소개하고 있습니다.

An ENUM is a string object with a value chosen from a list of permitted values that are enumerated explicitly in the column specification at table creation time.

ENUM 자료형을 사용할 때 얻을 수 있는 장점은 다음과 같습니다.

  1. Text(varchar,char,text)로 값을 저장하는것에 비해 자료의 정합성을 높힐 수 있다
  2. Text + FK 으로 설계하는 것에 비해 Table수를 줄일 수 있다.
  3. enum index 를 통한 연산을 사용할 수 있다.

첫번째 장점은 부연설명이 필요없는 장점입니다. text field 컬럼은 사실상 너무 많이 가능성이 열려있는 컬럼이기 때문에 아무리 응용프로그램에서 정합성을 체크해도 조금만 실수하면 바로 엉뚱한 값들이 들어와 속을 썩이기 마련입니다. 두번째는 조금 논쟁이 있을 수 있는 내용입니다. 사실 앞서 설명한 정합성에 대한 문제를 정석으로 해결하는 방법은 해당 컬럼에 들어올 수 있는 값을 row로 가진 별도의 자료 테이블을 생성하고 그 컬럼에서 FK 제약 조건을 통해 정합성을 확보하는 방식입니다. 이 방식은 어떤 DBMS에서도 사용할수 있는 방법이기 때문에 이식성이 뛰어나다는 장점이 있습니다. 하지만 그런 형태의 제약의 수가 아주 많다면 그 수만큼 테이블이 늘어나거나 혹은 메타정보를 관리하는 별도 테이블을 두고 관리해야 하기 때문에 번거롭습니다. 하지만 ENUM자료 형을 사용할 경우 상대적으로 적은 비용으로 원하는 바를 얻을 수 있기 때문에 경우에 따라서는 훌륭한 대안입니다. 세번째 장점은 의외로 모르는 분들이 많은 기능이지만 개인적으로 MySQL ENUM 자료형의 가장 멋진 기능이라고 생각하는 기능입니다. enum index란 ENUM으로 정의된 컬럼에 입력과 출력에 있어서 지정된 ‘값’ 자체 뿐만 아니라 색인(index)값을 이용해 해당 값에 접근할 수 있는 색인을 말합니다. 예를 들면

+-------+-------------------+------+-----+---------+----------------+
| Field | Type              | Null | Key | Default | Extra          |
+-------+-------------------+------+-----+---------+----------------+
| id    | int(11) unsigned  | NO   | PRI | NULL    | auto_increment |
| c1    | enum('a','b','c') | YES  |     | NULL    |                |
+-------+-------------------+------+-----+---------+----------------+

다음 질의와 결과는 아래와 같습니다.

SELECT id,c1,c1+0 FROM `t1`
+----+------+------+
| id | c1   | c1+0 |
+----+------+------+
|  1 | a    |    1 |
|  2 | b    |    2 |
|  3 | c    |    3 |
+----+------+------+

그후 다음 질의와 결과는 다음과 같습니다.

INSERT INTO t1(c1) VALUES(1);
SELECT id,c1,c1+0 FROM `t1`
+----+------+------+
| id | c1   | c1+0 |
+----+------+------+
|  1 | a    |    1 |
|  2 | b    |    2 |
|  3 | c    |    3 |
|  4 | a    |    1 |
+----+------+------+

이처럼 SELECT 문에서 ENUM 자료형 컬럼에 숫자 연산을 하면 색인값을 반환합니다. 마찬가지로 INSERT / UPDATE 문에서 ENUM 자료형 컬럼에 색인을 넣으면 자동으로 해당 컬럼을 넣어줍니다. 이런 특징은 특히 웹 프로그래밍에서 HTML의 Input 요소중 Radio 요소와 아주 궁합이 잘 맞습니다. 예를들면 위에 설명한 c1 컬럼에 값을 넣는 Radio 요소는 다음과 같이 단순하게 작성할 수 있습니다.

 <input type="radio" id="c11" name="c1" value="1"> a
 <input type="radio" id="c12" name="c1" value="2"> b
 <input type="radio" id="c13" name="c1" value="3"> c

이런 방식은 Radio 요소를 많게는 수백개 이상 다뤄야하는 그동안의 작업을 아주 단순하게 만들어 줬습니다.

Perl test code

이 포스트는 단순한 ENUM 자료형의 장점뿐만 아니라 그동안 이 자료형을 사용하면서 만났던 몇가지 문제들을 알아보고 재현할 수 있는 perl 코드를 작성해보는것을 목적으로 하고 있습니다. 이런 형태의 문제들은 주로 사용 초기에 발견되지 않고 오랜 기간 수정과 삭제를 반복하다가 발견하곤 하는데 문제에 대한 명확한 재현이 없이는 기존 자료에서 잘못된 내용을 찾는것도 쉽지 않고 같은 실수를 회피하도록 코드를 작성하는데도 많은 어려움이 있었기 때문에 따로 시간을 내서 발생하는 현상에 대해 재현할 수 있는 코드를 작성했습니다.

코드에는 평소에 제가 즐겨쓰는 몇가지 모듈이 사용됩니다. Test::Most, DBIx::Simple, SQL::Abstract, Test::DatabaseRow

  • Test::Most는 Test::More와 이름이 거의 흡사하지만 자주 사용하는 Test::Differences,Test::Deep,Test::Exception 등을 같이 불러주기 때문에 습관적으로 사용합니다.
  • DBIx::Simple + SQL::Abstract는 복잡하게 ORM을 사용하지 않고 간편하게 DB에 값을 주고 받을 때 사용합니다.
  • Test::DatabaseRow는 DB의 구조가 아닌 을 기준으로 검증코드를 만들고자 할때 사용합니다. 최근에 특정작업을 진행 하면서 많이 사용하게 되었습니다.
  • 테스트 코드를 작성할 때 반복사용되는 부분은 최대한 함수로 분리를 하고 실제 검증이 들어가는 부분은 가능하면 block({,})으로 scope를 분리시키면 비슷한 시도의 검증코드를 다수 만들때 복사해서 붙이기 용이합니다. ( “특히 subtest를 사용하면 test를 개념적으로 분리할 수 있어 더욱 좋다” by @JEEN_LEE )

Code

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#!/usr/bin/env perl
use Test::Most;
use Test::DatabaseRow;
use Test::Group;
use DBIx::Simple;
use SQL::Abstract;
use Const::Fast;

const my $DATABASE => 'testdb';
const my $TABLE    => 't1';
const my $COLUMN   => 'c1';

const my $DBHOST => '127.0.0.1';
const my $DBUSER => 'root';
const my $DBPASS => q();


my $db = DBIx::Simple->connect(
    "DBI:mysql:host=$DBHOST",
    $DBUSER, $DBPASS,
    {
        mysql_enable_utf8    => 1,
        mysql_auto_reconnect => 1,
    }
) or die;
$db->abstract = SQL::Abstract->new( { quota_char => '`', name_sep => '.' } );

local $Test::DatabaseRow::dbh = $db->dbh;

subtest "enum column에 컬럼 추가가 발생한경우(POST)" =>  sub {
    my %h = (
        1 => { n => 'a', i => 1 },
        2 => { n => 'b', i => 2 },
        3 => { n => 'c', i => 3 },
    );

    initialize(%h);
#    +-------+-------------------+------+-----+---------+----------------+
#    | Field | Type              | Null | Key | Default | Extra          |
#    +-------+-------------------+------+-----+---------+----------------+
#    | id    | int(11) unsigned  | NO   | PRI | NULL    | auto_increment |
#    | c1    | enum('a','b','c') | YES  |     | NULL    |                |
#    +-------+-------------------+------+-----+---------+----------------+

    insert_values(%h);
    test_column_value(%h);
    test_column_index(%h);

    query("ALTER TABLE `$TABLE` CHANGE `$COLUMN` `$COLUMN` ENUM('a','b','c','x','y','z')  NULL  DEFAULT NULL");

#    +-------+-------------------------------+------+-----+---------+----------------+
#    | Field | Type                          | Null | Key | Default | Extra          |
#    +-------+-------------------------------+------+-----+---------+----------------+
#    | c1    | enum('a','b','c','x','y','z') | YES  |     | NULL    |                |
#    +-------+-------------------------------+------+-----+---------+----------------+

    test_column_value(%h);
    test_column_index(%h);
};

subtest "enum column에 컬럼 추가가 발생한경우(PRE)" =>  sub {
    my %h = (
        1 => { n => 'a', i => 1 },
        2 => { n => 'b', i => 2 },
        3 => { n => 'c', i => 3 },
    );

    initialize(%h);
#    +-------+-------------------+------+-----+---------+----------------+
#    | Field | Type              | Null | Key | Default | Extra          |
#    +-------+-------------------+------+-----+---------+----------------+
#    | id    | int(11) unsigned  | NO   | PRI | NULL    | auto_increment |
#    | c1    | enum('a','b','c') | YES  |     | NULL    |                |
#    +-------+-------------------+------+-----+---------+----------------+

    insert_values(%h);
    test_column_value(%h);
    test_column_index(%h);

    query("ALTER TABLE `$TABLE` CHANGE `$COLUMN` `$COLUMN` ENUM('x','y','z','a','b','c')  NULL  DEFAULT NULL");

#    +-------+-------------------------------+------+-----+---------+----------------+
#    | Field | Type                          | Null | Key | Default | Extra          |
#    +-------+-------------------------------+------+-----+---------+----------------+
#    | c1    | enum('x','y','z','a','b','c') | YES  |     | NULL    |                |
#    +-------+-------------------------------+------+-----+---------+----------------+

    test_column_value(%h);
    test_column_index(%h); # Fail
};

subtest "enum column에 숫자 이름의 컬럼이 들어간 경우(1,2,3)" =>  sub {
    my %h = (
        1 => { n => '1', i => 1 },
        2 => { n => '2', i => 2 },
        3 => { n => '3', i => 3 },
    );

    initialize(%h);
#    +-------+-------------------+------+-----+---------+----------------+
#    | Field | Type              | Null | Key | Default | Extra          |
#    +-------+-------------------+------+-----+---------+----------------+
#    | id    | int(11) unsigned  | NO   | PRI | NULL    | auto_increment |
#    | c1    | enum('1','2','3') | YES  |     | NULL    |                |
#    +-------+-------------------+------+-----+---------+----------------+

    insert_values(%h);
    test_column_value(%h);
    test_column_index(%h);

    query("INSERT INTO t1(id,c1) VALUES(4,1)");
    all_row_ok(
        table       => $TABLE,
        where       => [ id => 4 ],
        tests       => [ $COLUMN => 1 ],
        description => "if input just 1"
    );

    query("INSERT INTO t1(id,c1) VALUES(5,'1')");
    all_row_ok(
        table       => $TABLE,
        where       => [ id => 5 ],
        tests       => [ $COLUMN => 1 ],
        description => "if input '1'"
    );
};


subtest "enum column에 숫자 이름의 컬럼이 들어간 경우(0,1,2)" =>  sub {
    my %h = (
        1 => { n => '0', i => 1 },
        2 => { n => '1', i => 2 },
        3 => { n => '2', i => 3 },
    );

    initialize(%h);
#    +-------+-------------------+------+-----+---------+----------------+
#    | Field | Type              | Null | Key | Default | Extra          |
#    +-------+-------------------+------+-----+---------+----------------+
#    | id    | int(11) unsigned  | NO   | PRI | NULL    | auto_increment |
#    | c1    | enum('0','1','2') | YES  |     | NULL    |                |
#    +-------+-------------------+------+-----+---------+----------------+

    insert_values(%h);
    test_column_value(%h);
    test_column_index(%h); #Fail

    query("INSERT INTO t1(id,c1) VALUES(4,1)");
    all_row_ok(
        table       => $TABLE,
        where       => [ id => 4 ],
        tests       => [ $COLUMN => 1 ],
        description => "if input just 1"
    );

    query("INSERT INTO t1(id,c1) VALUES(5,'1')");
    all_row_ok(
        table       => $TABLE,
        where       => [ id => 5 ],
        tests       => [ $COLUMN => 1 ],
        description => "if input '1'"
    );
};

done_testing();

sub query($) {
    my ($query) = @_;
    $db->query($query) && print STDERR ">> ", $query, "\n";
}

sub initialize {
    my %h = @_;

    my @cols = map { sprintf qq('$h{$_}->{n}') } sort keys %h;
    my $enum = sprintf(q{ENUM(%s)},join(',',@cols) );

    print STDERR "Database : $DATABASE\n";

    query("DROP DATABASE IF EXISTS `$DATABASE`");
    query("CREATE DATABASE `$DATABASE` DEFAULT CHARACTER SET `utf8`");
    query("USE `$DATABASE`");
    query("CREATE TABLE `$TABLE` (id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT) DEFAULT CHARACTER SET `utf8` ENGINE = `InnoDB`");
    query("ALTER TABLE `$TABLE` ADD `$COLUMN` $enum  NULL  DEFAULT NULL  AFTER `id`");

    print STDERR '-' x 80, "\n";
}

sub insert_values {
    my %h = @_;

    foreach my $k ( sort keys %h ) {
        $db->insert( $TABLE, { id => $k, $COLUMN => $h{$k}->{'n'} } );
    }
}

sub test_column_value {
    my %h = @_;

    foreach my $k ( sort keys %h ) {
        all_row_ok(
            table       => $TABLE,
            where       => [ id => $k ],
            tests       => [ $COLUMN => $h{$k}->{n} ],
            description => "row $k // $COLUMN is $h{$k}->{'n'}"
        );
    }
}

sub test_column_index {
    my %h = @_;

    foreach my $k ( sort keys %h ) {
        all_row_ok(
            sql => [ "SELECT $COLUMN + 0 as i FROM $TABLE WHERE id = ?", $k ],
            tests => [ i => $h{$k}->{'i'} ],
            description => "$k: index of $COLUMN is $h{$k}->{'i'}"
        );
    }
}

ENUM type side effects

위 테스트를 통해 알수있는 ENUM 자료형 사용시 주의해야할 사항은 다음과 같습니다.

  1. ENUM으로 정의된 컬럼에 내부 색인에 영향을 주도록 컬럼의 값 순서를 변경하면 DB에 저장된 ‘값’에는 영향이 없지만 색인에는 영향이 생깁니다.
  2. ENUM의 값으로 색인과 동일한 숫자를 사용 할 경우 인용부호의 사용에 주의하지 않으면 예상하는 결과와 다른 동작을 초래할 수 있습니다.

이 밖에 ENUM 자료형을 사용했을 때 고려해야 할 사항은 다음 링크를 참고하시기 바랍니다. 8 Reasons Why MySQL’s ENUM Data Type Is Evil

Conclusion

  • MySQL의 ENUM 자료형은 경우에 따라 유용하지만 발생할 수 있는 몇 가지 부작용에 주의해야한다.
  • 상황을 재현하는 테스트코드는 문제를 이해하고 공유하는데 도움을 준다.
  • Test::Most, Test::DatabaseRow 모듈은 테스트 코드 작성에 유용하다.

Jquery Note Plugin

| Comments

jquery note preview

demo

jquery.note

what the..

메모장입니다. 상태를 저장할 수 있고요. 상태에 따른 hook 이 많아서 유연하게 사용할 수 있고요. ajax 로 데이터를 주고 받아서 서버에 기록을 남길 수도 있습니다. 회사에서 필요해서 맨들었습니다. 여기저기 찾아봤는데 꼭 바라는게 없더라고요.

사용법

html 안에서 css, 랑 js 를 include 해주고요 $(document).ready 안에 슥슥해주면 됩니다.

[jquery.note] []
1
2
3
4
5
6
7
8
9
10
<link rel="stylesheet" href="css/jquery.note.css" type="text/css" media="screen" />
<script type="text/javascript" src="script/jquery-1.6.2.js"></script>
<script type="text/javascript" src="script/jquery.note.js"></script>
<script type="text/javascript">
  $(document).ready(function() {
    $("a[title=note]").each(function() {
      $(this).note();
    });
  });
</script>

coffeescript 로 작성했고요, css 는 scss 로 작성했습니다.

Hooks

[jquery.note hook example] []
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$(document).bind('afterReveal.note', function(e, data) {
  if (data.opts.notes.length === 0) {
    return $.ajax({
      type: 'GET',
      dataType: 'json',
      url: '/path/to',
      success: function(res, textStatus, jqXHR) {
        data.opts.notes = res;
        // res 는 아래처럼 구성됩니다.
        // [{"who":"hshong","date":"2012-01-20 19:23","status":"open"},{"who":"aanoaa","date":"2012-01-20 19:25","note":"다시하렴 처음부터"}]
        if (data.opts.notes.length !== 0) return $(data.owner).click();
      }
    });
  }
});

notes가 보여질때 갯수를 확인한다음에, 0개라면 서버로 질의해서 노트와, 상태정보를 받아와서 업데이트 해주고 reloading 합니다.

옵션에 따라 달리 동작할 수 있는데, 실제로 쓸거 아니면 알 필요 없습니다.

사용가능한 hook 은 아래와 같습니다.

  • init.note 초기화할때
  • afterClose.note 닫히자마자
  • beforeSend.note ajax 보내기전에
  • afterSuccess.note ajax 성공하자마자
  • beforeReveal.note 보여지기바로전에
  • afterReveal.note 보여지자마자
  • changeStatus.note open에서 close 되는것처럼 상태가 변경되고 나면

See also

마무리

jquery plugin 을 만들고자 하는 분이라면 한번 스윽 보셔도 괜찮을 것 같습니다.

javascript 잘하시는 어떤분이 제 코드를 보고 좀 까줬으면 하는 바람도 있습니다. 제가 영어가 딸려서 미국사람한테는 그런부탁 못하거든요.

정말 글 못쓰네요.. 어렵고요 아아아아.. 쓰다보면 나아지겟죠.

Octopress Usage

| Comments

octopress 에서 어떻게 글을 쓸 것인가?

ruby install

$ rvm install 1.9.2 && rvm --default use 1.9.2
$ gem install bundler

rvm 은 ruby 의 perlbrew 고요, bundler는 Carton 입니다.

git clone && bundle install

저장소의 remote 정보는 아래와 같습니다.

$ git remote -v
octopress   git://github.com/imathis/octopress.git (fetch)
octopress   git://github.com/imathis/octopress.git (push)
origin  git@github.com:silexkr/silexkr.github.com.git (fetch)
origin  git@github.com:silexkr/silexkr.github.com.git (push)

살렉스 여러분은 write 권한이 있습니다. clone 받으세여.

$ git clone git@github.com:silexkr/silexkr.github.com.git

그리고 의존모듈을 설치 합니다. && 글을 쓰기 위해선 source 브랜치를 사용합니다.

$ cd silexkr.github.com.git/
$ git checkout source
$ bundle install

rake setup_github_pages

write 할 수 있는 repository 주소를 octopress 에 등록해줘야 합니다.

$ rake setup_github_pages
Enter the read/write url for your repository: git@github.com:silexkr/silexkr.github.com.git

rake new_post

새글을 쓰기 위해선 직접 파일을 맨들어줘도 되지만 rake 유틸리티를 이용하세요. 편합니다. 요렇게요.

$ rake new_post["Octopress usage"]
mkdir -p source/_posts
Creating new post: source/_posts/2012-01-20-octopress-usage.markdown

Octopress usage 가 제목이고요, source/_posts/2012-01-20-octopress-usage.markdown 파일이 만들어졌습니다.

메타정보입력

source/_posts/2012-01-20-octopress-usage.markdown 파일을 열어보면 젤 위에 요렇게 나옵니다.

---
layout: post
title: "Octopress usage"
date: 2012-01-20 01:44
comments: true
categories:
---

저희는 여러명이서 쓸것이기 때문에 author 정보도 입력하면 좋습니다. default 는 Silex 입니다.

카테고리는 여러개를 입력할 수 있습니다. Blogging Basics 에 좋은 예가 있습니다.

1
2
3
4
5
6
7
8
9
10
11
# One category
categories: Sass

# Multiple categories example 1
categories: [CSS3, Sass, Media Queries]

# Multiple categories example 2
categories:
- CSS3
- Sass
- Media Queries

저는 이글을 쓰면서 요렇게 했습니다.

---
author: 홍형석
layout: post
title: "Octopress usage"
date: 2012-01-20 01:44
comments: true
categories: octopress
---

글작성

markdown 문법을 따릅니다. Plugins를 활용해서 이미지도 옇고 코드도 옇습니다.

local 에서 확인

$ rake preview
Starting to watch source with Jekyll and Compass. Starting Rack on port 4000
~~~ 중략 ~~~
>>> Compass is polling for changes. Press Ctrl-C to Stop.

문서보면 rake watch 도 있는데여, scss, css 건드릴거 아니면 안띄워줘도 됩니다.

rake deploy

로컬에서 확인이 완료 되었으면, deploy 합니다.

$ rake deploy

github 에서 성공메세지를 받으면 silexkr.github.com 에서 바로 확인할 수 있습니다.(대부분 바로 반영되더라고요)

git add, commit, push

deploy 와는 별도로 원본글을 커밋하고 이력을 관리합니다.

$ git add .
$ git commit -m "posting Octopress usage"
$ git push origin source

주의할게 있는데요. master 브랜치가 아니라 source 로 push 해야 합니다.

master는 원본이 변환된 static 파일들이 모여 서비스 되는 브랜치고요, source에서 원본을 관리합니다.

생각해보니까 master는 어차피 deploy 할때 HEAD 라서 그냥 push 해도 되겠군여.

이제 뭘 하징?

네 글을 써주세요.

See also

Hello World

| Comments

소개

건대입구에서 소프트웨어 장사하는 Silex(일명 용키)입니다.

멤버

  • 유용빈 용사장이라 불리우고요, 사장입니다. 맥북프로 씁니다.
  • 김도형 거침없이 배우는 펄 번역했고요, vaio 에 arch linux 씁니다.
  • 이민선 젊은 여자고요, 진님이 쓰다버린 흰둥이 맥북프로 씁니다.
  • 홍형석 emacs 쓰고요, 띵크패드에 우분투 씁니다.
  • 이종진 일본 IT 업체에서 일하다 온 유부남이고요, 일제 맥북에어 씁니다.
  • 조한영 도형님 문하생입니다.(직원은 아닌 것 같습니다.) 듣보 노트북에 우분투 씁니다.

이중에 CPAN Author 가 4명 입니다.

하는 일

지금은 perl로 private한 웹서비스 맨들고 있습니다. 웹서비스말고 다른 것도 할 수 있습니다. 자세한건 용사장님이 언젠가 포스팅 하지 않을까 그래 생각하고 잇습니다.

See also

silexkr - facebook

we are silex