2012년 10월 29일 월요일

주목할만한 Meteorite package들

meteorite 는 meteor 에서 정식으로 지원하지 않는 사도의 길이지만
그런 거 언제 중요했던가. 달면 뱉고 쓰면 삼키는 것이지 :)
meteor 자체가 여러가지로 부족하고 제약도 많은 프레임워크다.
하지만 워낙 매력있는 놈이다보니 자체 package manager 같은 게 만들어질 정도.
마치 Account package 처럼 meteor 1.0 찍기 전에 정식 기능으로 편입되지 않을까 하는 기대를 해본다.

지극히 개인적인 관점에서 흥미있는 것들이라 다른 것도 볼만하다.
혹은 없으면 직접 만들어서 커미터가 되어보자!

aloha-editor
Aloha Editor for Meteor

wysihtml5 쓰는데 문제가 너무 많아서 고려중

accounts-instagram
Login service for Instagram accounts.


OAuth 연동하는 예제로 볼만함

headly
meteor package to handle facebookexternalhit responses (for og:metatags use…

Meteor 에선 head 부분이 template 으로 들어가지 않아. 프로그램에서 접근이 불가하다.
이를 해결해주는 앱. facebook open graph 연동을 위해 필요함

node-modules
Node module hacks encapsulated
node-modules 을 쓰기 위해서 public 에 넣는 꼼수(tests 에 넣어도 되지만 package 할때 문제)를 피할 수 있게 직접 절대경로를 지정할 수 있음.


codemirror
CodeMirror repackaged for Meteor

jsbin등에서도 쓰는 code mirror 의 meteorite 패키지. 공동 문서작업 용도로 쓸만할 것으로 보임.

moment
Moment.js packaged for Meteor
시간 관련 함수를 깔끔하게 정리해놓은 moment. 추천

angularjs
Smartpackage for the use of angular on top of meteor
angular.js의 강력한 2-way binding 을 쓸 수 있도록 해주는 angular package. backbone 과 handlebars를 안쓰고 구현할 수 있다.

여전히 늘어나고 있는 추세고 업데이트도 잘되고 있다.
meteor도 그렇고 meteorite package들도 그렇듯이 이들이 사용하고 있는 js 라이브러리들을 잘 살펴보면 최근 경향을 한눈에 살펴볼 수 있어서 좋다.

2012년 10월 19일 금요일

map과 reduce를 사용하여 distinct(group by) 하는 방법

oracle 같은 RDB 를 다루던 시절엔

user | dept
-----------
john | 0000
tom | 0001
jack | 0000
jane | 0001

과 같은 형태의 테이블일때 부서의 목록을 뽑아 내고자 group by 나 distinct 를 사용하여

dept
-----
0000
0001

이와 같이 뽑아내곤 했는데

noSQL 을 사용하거나 array collection 형태의 데이터를 다룰때에도 이런 작업을 해야할 경우가 있다.

일단 예제로 다뤄볼 대상 collection 의 구조를 가정해 보자.
documents = [
  {
    _id: "........",
    message: "......",
    user : {
      _id: "........",
      profile: {
        displayName: "....."
      }
    }
]
흔한 게시판 형태의 데이터 구조라고 볼 수 있는데
각 documents 마다 고유한 _id 를 가지고 있고 해당 documents 를 작성한 user 객체가 각각 들어있는 경우라고 볼 수 있다.
그렇다면 documents 를 작성한 user 의 목록을 뽑아낼 수는 없을까?

node 콘솔이나 브라우저용 개발 툴에서 복사 붙이기 용으로 코드를 예제 코드를 한번 넣어보자면
documents = [ { _id: "doc0001", message: "document 01", user: { _id: "user0001", profile: { displayName: "john", } } }, { _id: "doc0002", message: "document 02", user: { _id: "user0002", profile: { displayName: "tom", } } }, { _id: "doc0003", message: "document 03", user: { _id: "user0001", profile: { displayName: "john", } } },
{ _id: "doc0004", message: "malformat" },
]
이런 식으로 넣었을때
[
Object
  1. _id"doc0001"
  2. message"document 01"
  3. userObject
    1. _id"user0001"
    2. profileObject
      1. displayName"john"
      2. __proto__Object
    3. __proto__Object
  4. __proto__Object
,
Object
  1. _id"doc0002"
  2. message"document 02"
  3. userObject
    1. _id"user0002"
    2. profileObject
      1. displayName"tom"
      2. __proto__Object
    3. __proto__Object
  4. __proto__Object
,
Object
  1. _id"doc0003"
  2. message"document 03"
  3. userObject
    1. _id"user0001"
    2. profileObject
      1. displayName"john"
      2. __proto__Object
    3. __proto__Object
  4. __proto__Object
,
Object
  1. _id"doc0004"
  2. message"malformat"
  3. __proto__Object
]
요런 구성으로 들어갈 것이다.
마지막 doc0004는 user 가 없는데 당연히 저런 데이터가 있을 수 있다.
왜냐면 관계형 DB 처럼 고정 스키마가 없기 때문이다.

그러면 먼저 map 을 이용하여 user만 추출해보도록 하자.
documents.map(function(v) { return v.user && v.user.profile && v.user.profile.displayName ? v.user:null; });
[
Object
  1. _id"user0001"
  2. profileObject
    1. displayName"john"
    2. __proto__Object
  3. __proto__Object
,
Object
  1. _id"user0002"
  2. profileObject
    1. displayName"tom"
    2. __proto__Object
  3. __proto__Object
,
Object
  1. _id"user0001"
  2. profileObject
    1. displayName"john"
    2. __proto__Object
  3. __proto__Object
, null]
마지막 user가 없는 경우는 조건식을 통해 null을 반환하도록 하였다.
그러면 이제 중복을 제거할 차례다.
중복을 제거하기 위해 reduce 를 사용하자.
reduce는 두개의 인자를 갖는 function을 사용하는데

document.reduce(function(a,b) {
  return ...
});

이런 식으로 사용한다.
reduce 함수의 재미있는 점은 인자를 가져가는 방식이다

index | a | b
0 | array[0] | array[1]
1 | 0번째의 return 값 | array[2]
2 | 1번째의 return 값 | array[3]
......
n-1 | n-2번째의 return 값 | array[n]
------------------------------------
최종값 = n-1번째의 return 값
되시겠다.

이런 특성을 이용하여 중복이 발생하지 않는 user의 array를 만든다면 다음과 같은 전략을 짤 수 있다.

  1. reduce function안에서 a,b 두개를 받는다.
  2. a 즉 지난번 reduce 실행값 중 b가 있는지 검사한다.
  3. 있으면 a 자신을 반환한다.
  4. 없으면 a에 b를 추가하고 a 자신을 반환한다.

단순한 로직인데 a가 일단 array라는 가정이 필요하니 첫번째 시도때 a를 []로 초기화 해줘야하고 b가 a안에 있는지 여부를 검사하기 위해 some 이라는 function 을 사용하여 _id가 같은 것을 발견하면 true를 주게끔 해보자.
아, 그리고 null은 의도한 결과가 아니므로 당연히 예외처리.

documents.map(function(v) { return v.user && v.user.profile && v.user.profile.displayName ? v.user:null; }).reduce(function(a,b) { a=[].concat(a); if(b && !a.some(function(v) { return v._id===b._id; })) { a.push(b); } return a; });
[
Object
  1. _id"user0001"
  2. profileObject
    1. displayName"john"
    2. __proto__Object
  3. __proto__Object
,
Object
  1. _id"user0002"
  2. profileObject
    1. displayName"tom"
    2. __proto__Object
  3. __proto__Object
]



어떠십니까? 엘레강스하십니까?

쓰고보니 재탕 느낌이 들어서 검색해보니 예전에 이런 글을 쓴적이 있긴 하네
http://spectrumdig.blogspot.kr/2012/03/javascript-union.html
그래도 map이랑 같이 썼으니 인정.


드디어 meteor 0.5.0!

이전에 썼던 auth 관련 글은 죄다 잊으십시오.
그냥 meteor update 하면 됩니다.
Accounts 기능이 정식으로 들어갔습니다.
일반 로그인, OAuth 로그인이 정말 간편하게 됩니다.
사용법은 http://docs.meteor.com/#accounts_api 참조하세요.

만세! 만세! 만만세!

screencast 도 꼭 보세요. 두번 보세요. 아니 세번 보세요.

만세! 만세! 만만세!


2012년 10월 13일 토요일

deploy 서버에서 mongo data를 dump 하는 법

meteor deploy YOURSITE.meteor.com

이 명령으로 디플로이 했을 시
테스트로 잠깐 쓰다가 제대론 서버에 올려서 돌리고 싶을 수 있다.

meteor mongo -U YOURSITE.meteor.com
하면 접속 정보가 나온다.

내 경우엔
mongodb://client:72c39268-747e-9380-7538-ed317e096c69@skybreak.member1.mongolayer.com:27017/YOURSITE_meteor_com
이렇게 나오는데 //client: 뒤에 있는 놈이 암호.
1분 마다 바뀐다.

mongodump -u client -h skybreak.member1.mongolayer.com:27017 -d YOURSITE_meteor_com -p 72c39268-747e-9380-7538-ed317e096c69

잽싸게 접속하는 것이 포인트.
각자 상황에 따라 URL 이 다를 수 있다.

mongodump 로 내려놓은 데이터를 복원하려면 mongorestore 를 사용한다.
만일 로컬 환경에 복원한다면

> /usr/local/bin/meteor mongo -U
mongodb://127.0.0.1:3002/meteor

로 환경을 확인한 후


mongorestore --host 127.0.0.1:3002 ./dump/YOURSITE_meteor_com --drop -d meteor


와 같이 복원하면 OK

만일 특정 collection 별로 하려면 mongoimport / mongoexport 를 사용하면 된다.

2012년 10월 12일 금요일

meteor auth branch 가 RC (Release candidated) 버전으로!

기존 문서(https://github.com/meteor/meteor/wiki/Getting-Started-with-Auth)는 deprecated인 상태이므로 http://auth-docs.meteor.com/ 쪽으로 봐야합니다.
Accounts 객체에 대한 내용이 있습니다.

지난 버전에서 사용하던 Accounts.configuration 객체는 Accounts.loginServiceConfiguration 으로 변경해야합니다.
내용은 여전히 아래의 구조로 insert 하여야합니다.
{
  "service":"facebook",
  "appId":"xxx4xxxx8x8xxxx",
  "secret":"3xxf8xxxx1d4cxxxx018dfxxxxb6a7bc"
}
서비스명(facebook, google, twitter, github, weibo 등등), 앱 아이디, 앱 비밀번호 넣는거죠.

일단 branch가 auth 에서 드디어 devel (https://github.com/meteor/meteor/tree/devel)으로 통합이 되었습니다.
다음이나 다다음버전 쯤에선 고생하지 않고 한방에 설치 가능하겠네요.

아직까지는 Slow start(https://github.com/meteor/meteor#slow-start-for-developers) 방식으로 설치하여야합니다.

지난 글(http://spectrumdig.blogspot.kr/2012/08/meteor-039.html) 참조하시고
변경된 부분은 기존에 git clone 을 통해 받았던 경로에서

git checkout -t origin/auth

대신

git checkout -t origin/devel
을 하시고

git pull
./install.sh
하시면 됩니다.

뒤집어지면 다시 포스팅 하겠습니다. xD

2012년 10월 8일 월요일

Fiber, future, 그리고 co-routine

맞춤법 검사를 위해 서버사이드에서 http.get 을 할 일이 있는데
meteor 에서 지원하는 future(node-fiber 라이브러리에 포함) 를 사용했다.

이거 블록으로 되어서 동시접속시에 망하는 거 아니냐라는 의혹을 불식시키기 위해
간단하게 코드를 짜보았다.

meteor create future
cd future

vi future.js
하고 아래와 같이 부다다.

if (Meteor.isClient) {
  Template.hello.greeting = function () {
    return "Welcome to future.";
  };

  Template.hello.events({
    'click input' : function () {
      // template data, if any, is available in 'this'
      if (typeof console !== 'undefined')
        console.log("You pressed the button");
    }
  });
}

if (Meteor.isServer) {
  Future = Npm.require('fibers/future');
  Meteor.startup(function () {
    // code to run on server at startup
    Meteor.methods({
      'future':function() {
        console.log('future start:'+Date.now());
        var fut = new Future();
        Meteor.setTimeout(function() {
          console.log('callback end:'+Date.now());
          fut.return('yahoo:'+Date.now());
        }, 1000);
        console.log('future end:'+Date.now());
        return fut.wait();
      }
    });
  });
}

(노란색 형광펜으로 최근 Meteor 적용사항 수정. 기존 ret가 사라지고 return로 바뀌었다)
future 라는 서버사이드 method 를 만들었으니 브라우저 콘솔에서 meteor.call('future') 를 호출해서 결과를 관찰해보도록 하자.

client side console
>> Meteor.call('future', function(err, result) { console.log(result) }); console.log('after call:'+Date.now());
after call:1349676901674
yahoo:1349676902879

server side log
future start:1349676901679
future end:1349676901679
callback end:1349676902679

after call -> future start | future end -> callback end | yahoo

순으로 실행이 된다.
call 후 다음 실행은 계속 진행.

Continuation-Passing Style 의 경우 future의 인자로 callback 을 계속 끌고 들어가야하므로 method signature 가 동기와 비동기 차이가 발생하지만 이 경우엔 그렇지 않다.

2012년 10월 5일 금요일

headless ubuntu for dummies


재료
가상환경 빨리 구축하기.
http://www.kbench.com/software/?flag=1&pcc=0&pg=1&no=48219
VirtualBox 는 여기에서

ftp://ftp.sayclub.com/ubuntu-releases/12.10/
우분투 바이너리는 여기.

나의 선택은?
ubuntu-12.10-beta2-server-i386.iso

1.
[!] Configure the network
Hostname:
ubuntu <default>
2.
[!!] Set up users and passwords
Full name for the new user:
noder <풀 이름>
3.
[!!] Set up users and passwords
Username for your account:
noder <계정명>

그다음 password 입력, 만일 단순한 패스워드를 넣으면 weak password 쓸 거냐고 묻는데 알아서.

4.
[!!] Partition disks
Write the changes to disks and configure LVM?
<Yes>

5.
[!!] Partition disks
Write the changes to disks?
<Yes>

6.
[!] Configure the package manager
HTTP proxy information (blank for none):
<Continue>

설치 후 해당 버추얼 머신의 설정을 선택
설정/네트워크/어댑터1
다음에 연결됨(A) - 브리지 어댑터

확인

다시 VirtualBox 머신을 기동하면 네트웍 설정 변화 감지.
로그인 후 ifconfig 에서 eth0 를 보면
inet addr 이 별도로 생성되었음. 내 경우 192.168.0.18

sudo aptitude update
sudo aptitude install openssh-server

연타해서 openssh 설치

그리고 ssh 프로그램으로 접속 확인.

잘되면 sudo init 0 해서 머신 종료
headless 모드로 띄우자.

VmHeadless.exe -s <가상장치명>
아님 조용하게
"C:\Program Files\HSTART\hstart64.exe" /NOCONSOLE /SILENT "C:\Program Files\Oracle\VirtualBox\vboxheadless.exe --startvm MYVM"

이렇게 띄울 수도 있다고 한다.

VmServiceControl 다운로드 후 실행. 반드시 관리자 모드로

2012년 10월 4일 목요일

meteor 에서 npm 을 이용해 node_modules 을 사용하는 법

제대로 쓴 곳이 없어서 결국 직접 해보고 알아내는 수 밖에 없었는데
서버쪽 moment.js 를 쓰고 싶어서
server/client 쪽 양쪽 다 영향을 안 받는 디렉토리 중 하나인 tests 디렉토리 아래서

npm install moment

를 실행하고

npm init

해서 대충 package.json 파일을 만들고


Meteor.startup(function () {
  // server side require
  var require = __meteor_bootstrap__.require;
  var path = require('path');
  var base = path.resolve('.');
  var moment = require(path.resolve('.')+'/tests/node_modules/moment');

이런 식으로 했더니 일단 잘 되더라.
근데 문제는 meteor deploy 를 하거나 meteor bundle 로 일반 node.js 프로젝트 화 했을때
test 디렉토리를 프로젝트에 포함시키지 않아서 서버쪽 require 를 쓸 수 없게 된다.

그래서 매우 찜찜하지만 public 경로 안에 node_modules 를 넣는 것 이외엔 방법이 없다.
public
|-node_modules
|-.gitignore
|-package.json

요런식으로 구성했다.
그리고 서버쪽 코드를 좀 많이 손을 대야하는데 로컬에서도 돌아가고 디플로이 한 서버에서도 돌아가도록 맞춰줬다.

Meteor.startup(function () {
  // server side require
  var require = __meteor_bootstrap__.require;
  var path = require('path');
  var base = path.resolve('.');
  var isBundle = path.existsSync(base + '/bundle');
  console.log('deploy mode: %s', isBundle ? 'bundle' : 'meteor');
  var modulePath = base + (isBundle ? '/bundle/static' : '/public') + '/node_modules';

  // code to run on server at startup
  var moment = require(modulePath + '/moment');

bundle 경로를 기준으로 deploy 상태를 판별하고 개발시엔 public/node_modules 을 기준으로 deploy 시엔 bundle/static/node_modules 를 기준으로 외부 모듈을 require 하면 된다.

서버사이드 로그도 찍어보고 fs.readdir로 일일이 디렉토리 구조까지 열어보면서 욕 좀 봤다.