Dragonkun in Tistory

SEARCH RESAULT : 글 검색 결과 - 개발 (총 4개)

POST : 프로그래밍/개발

Flex와 Rails, 그리고 REST

이 글은 작업 중에 있는 글입니다.
차후에 설명을 붙이도록 하고 우선 소스만 올려둡니다.

Internal Action Script 가 포함된 Flex mxml 파일

<?xml version="1.0"?>
<!-- 게시판 -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="svcPosts.send();"
>
    <mx:HTTPService id="svcPosts" url="http://localhost:3000/posts.xml" method = "GET" resultFormat="e4x" result="postsResultHandler(event)" fault="postsFaultHandler(event)">
    <mx:request>
        <limit>20</limit>
    </mx:request>
    </mx:HTTPService>
    <mx:HTTPService contentType="application/xml"
                     id="svcCreatePost"
                     url="http://localhost:3000/posts"
                     useProxy="false" method="POST">
        <mx:request xmlns="">
            <post>
                <name>{tAuthor.text}</name>
                <subject>{tSubject.text}</subject>
                <body>{tBody.text}</body>
            </post>
        </mx:request>
    </mx:HTTPService>
    <mx:HTTPService id="svcDeletePost"
                           result="svcPosts.send();"
                           url="http://localhost:3000/posts"
                           method="POST"
                           useProxy="false"/>
    <mx:HTTPService id="svcEditPost"
                           result="svcPosts.send();"
                           url="http://localhost:3000/posts"
                           method="POST"
                           useProxy="false">                          
        <mx:request xmlns="">
            <post>
                <name>{oldAuthor.text}</name>
                <subject>{oldSubject.text}</subject>
                <body>{oldBody.text}</body>
            </post>
        </mx:request>                          
    </mx:HTTPService>
    <mx:Style>
        Panel { font-size: 12pt }
    </mx:Style>   
    <mx:Script>
    <![CDATA[
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;
    import mx.controls.Alert
    import mx.events.CloseEvent;
   
    [Bindable]
   
    private var posts:XML;
   
    public function postsResultHandler(event:ResultEvent):void
    {
        posts = event.result as XML
        //Alert.show(posts);
    }
   
    public function postsFaultHandler(event:FaultEvent):void
    {
        Alert.show(event.fault.message, "Could not load posts!");
    }

    private function deleteHandler(event:Event) : void
    {
      Alert.show("정말 이 항목을 삭제하시겠습니까?", "포스트 삭제", 3, this,
      function(event:CloseEvent):void
      {
        if (event.detail==Alert.YES)
            svcDeletePost.url = 'http://localhost:3000/posts/'+dgPosts.selectedItem.id+'.xml';
            svcDeletePost.send({id: dgPosts.selectedItem.id, _method:'DELETE'});
       });
    }

    ]]>
    </mx:Script>   

    <mx:Panel title="Flexible Board" height="100%" width="100%"
        paddingTop="10" paddingLeft="10" paddingRight="10">

        <mx:Label width="100%" color="black"
            text="게시판입니다."/>

        <mx:DataGrid id="dgPosts" width="100%" height="100%" rowCount="20" dataProvider="{posts.post}">
            <mx:columns>
                <mx:DataGridColumn dataField="id" headerText="No" width="40" textAlign="center" />
                <mx:DataGridColumn dataField="name" headerText="이름" width="100" textAlign="center" />
                <mx:DataGridColumn dataField="subject" headerText="제목"/>
                <mx:DataGridColumn dataField="created_at" headerText="글 쓴 날짜" width="150" textAlign="center" />
                <mx:DataGridColumn dataField="updated_at" headerText="변경된 날짜" width="150" textAlign="center"/>
            </mx:columns>
        </mx:DataGrid>

        <mx:Form width="100%" height="100%">
            <mx:FormItem label="이름">
                <mx:TextInput text="{dgPosts.selectedItem.name}" id="oldAuthor" />
            </mx:FormItem>
            <mx:FormItem label="제목">
                <mx:TextInput text="{dgPosts.selectedItem.subject}" id="oldSubject" />
            </mx:FormItem>
            <mx:FormItem label="글 쓴 날짜">
                <mx:Label text="{dgPosts.selectedItem.created_at}"/>
            </mx:FormItem>
            <mx:FormItem label="변경된 날짜">
                <mx:Label text="{dgPosts.selectedItem.updated_at}"/>
            </mx:FormItem>           
            <mx:FormItem label="내용">
                <mx:TextArea width="400" height="100" text="{dgPosts.selectedItem.body}" id="oldBody" />
            </mx:FormItem>
            <mx:Button label="글 수정"
                   click="svcEditPost.url='http://localhost:3000/posts/'+dgPosts.selectedItem.id+'.xml';svcEditPost.send({_method:'PUT'});svcPosts.send();"/>
            <mx:Button label="글 삭제"
                   click="deleteHandler(event);"/>                  
        </mx:Form>
       
        <mx:Form width="100%" height="100%">
            <mx:FormItem label="이름">
                <mx:TextInput id="tAuthor" />
            </mx:FormItem>
            <mx:FormItem label="제목">
                <mx:TextInput id="tSubject" />
            </mx:FormItem>
            <mx:FormItem label="내용">
                <mx:TextArea id="tBody" width="400" height="100"/>
            </mx:FormItem>
            <mx:Button label="글 등록"
                   click="svcCreatePost.send();svcPosts.send();"/>
        </mx:Form>
       
        <mx:ControlBar horizontalAlign="center">
            <mx:Button label="새로 고침" click="svcPosts.send();"/>
        </mx:ControlBar>
    </mx:Panel>
</mx:Application>       

레일스 어플리케이션의 DB 스키마
create_table "posts", :force => true do |t|
    t.string   "name"
    t.string   "subject"
    t.text     "body"
    t.datetime "created_at"
    t.datetime "updated_at"
end

레일스 어플리케이션 컨트롤러(REST)
class PostsController < ApplicationController
  # GET /posts
  # GET /posts.xml
  def index
    @posts = Post.find(:all, :order => 'created_at desc')

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @posts.to_xml(:dasherize => false) }
    end
  end

  # GET /posts/1
  # GET /posts/1.xml
  def show
    @post = Post.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @post.to_xml(:dasherize => false) }
    end
  end

  # GET /posts/new
  # GET /posts/new.xml
  def new
    @post = Post.new

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @post.to_xml(:dasherize => false) }
    end
  end

  # GET /posts/1/edit
  def edit
    @post = Post.find(params[:id])
  end

  # POST /posts
  # POST /posts.xml
  def create
    @post = Post.new(params[:post])

    respond_to do |format|
      if @post.save
        flash[:notice] = 'Post was successfully created.'
        format.html { redirect_to(@post) }
        format.xml  { render :xml => @post.to_xml(:dasherize => false), :status => :created, :location => @post }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @post.errors, :status => :unprocessable_entity, :dasherize => false }
      end
    end
  end

  # PUT /posts/1
  # PUT /posts/1.xml
  def update
    @post = Post.find(params[:id])

    respond_to do |format|
      if @post.update_attributes(params[:post])
        flash[:notice] = 'Post was successfully updated.'
        format.html { redirect_to(@post) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @post.errors, :status => :unprocessable_entity }
      end
    end
  end

  # DELETE /posts/1
  # DELETE /posts/1.xml
  def destroy
    @post = Post.find(params[:id])
    @post.destroy

    respond_to do |format|
      format.html { redirect_to(posts_url) }
      format.xml  { head :ok }
    end
  end
end

생성된 SWF
생성된 SWF 파일 결과


top

tags

flex, Rails, rest, Ruby on Rails, 개발, 레일스, 루비, 삽질, , 프로그래밍

posted at

2008/08/13 09:08


POST : 프로그래밍/개발

우분투 Hardy에서 Phusion Passenger(A.K.A mod_rails) 설정하기

설치
우선 Phusion Passenger (A.K.A mod_rails)를 설치합니다.
sudo gem install passenger

우분투에서는 설치 후 패스가 제대로 걸려 있지 않습니다.
/var/lib/gems/1.8 아래에 설치가 되더군요.
아래와 같이 아파치 모듈을 설치하는 명령어를 실행합니다.
(apache2-prefork-dev 같은 아파치 개발 패키지가 필요할지도 모릅니다.)
sudo /var/lib/gems/1.8/bin/passenger-install-apache2-module

엔터 몇 번을 치면 자동으로 모듈을 빌드하고 설치를 끝마칩니다.
그리고 아래와 같은 설정을 /etc/apache2/mods-availabe/mod_rails.load 라는 파일을 만들어 놓습니다.
LoadModule passenger_module /var/lib/gems/1.8/gems/passenger-2.0.3/ext/apache2/mod_passenger.so
PassengerRoot /var/lib/gems/1.8/gems/passenger-2.0.3
PassengerRuby /usr/bin/ruby1.8

쉘에서 아래와 같은 명령으로 mod_rails 모듈을 활성화 시킵니다.
sudo a2enmod mod_rails

그리고 아래와 같이 아파치 설정을 합니다.
전 포스팅에서 다뤘던 redmine 을 예제로 합니다.

우선 /etc/apache2/sites-enable/default 파일을 아래에 아래와 같이 수정합니다.
<VirtualHost *>
  ServerName www.yourdomain.com
  DocumentRoot /home/redmine/public
  RailsEnv development
  ....(이하 생략)
</VirtualHost>

아파치를 설정을 다시 불러들입니다.
sudo /etc/init.d/apache2 force-reload

이걸로 설정이 끝났습니다.
다른 어떤 배포 환경보다도 간단하게 설치/설정이 가능합니다.
nginx+몽그렐 클러스터, 아파치+프록시 밸런서+몽그렐 클러스터 등등의 설정에 비하면 정말 간단해진 것을 알 수 있습니다.

우아한 재시작
레일스 어플리케이션을 구동하다 보면 새로운 모델을 추가한다던가 새로운 플러그인을 설치한다던가, 웹 서버를 재시작해야 할 일이 종종 생깁니다.
기존에는 우아하게 재시작하기 위해서 seesaw 와 같은 gem으로 몽그렐 클러스터를 관리했었는 데, mod_rails 는 한 술 더 뜹니다.  그저 어플리케이션 디렉토리에서 아래와 같은 명령어면 됩니다.
touch tmp/restart.txt

이렇게 restart.txt 파일만 만들어 놓으면 접속이 있을 때, 새로 레일스 설정을 불러들입니다.

어느 정도 mod_rails 를 이용하여 어플리케이션을 돌려봤는 데, 성능도 좋고 정말 안정적으로 돌아갑니다.
예전 초창기에 FCGI 썼을 때의 악몽(정말 이유를 알 수 없는 500에러...) 따위는 이제 생각하지 않아도 됩니다.
게다가 서버 재시작을 위해서 루트 권한도 필요없으니 한 번 설정만 해 놓으면 사용자 계정에서 무리없이 쓸 수도 있구요.
루비의 배포환경도 나날이 발전하고 있습니다.  아직 완벽해졌다고는 할 수 없지만, 이 정도만 되고 꽤나 편리해 진 셈이지요.
이올린에 북마크하기(0) 이올린에 추천하기(0)
top

POST : 프로그래밍/개발

Ruby의 프로젝트 관리 어플리케이션, Redmine - 설치

redmine의 홈페이지

오늘 아침 출근해서 이것 저것 웹 서핑하다가 우연히 redmine 의 존재를 알게 되었습니다.
Python 의 trac 처럼 위키, scm, 이슈 트래킹 시스템 등등이 포함되어 있는 통합 프로젝트 관리 어플리케이션이죠.
동아리 홈페이지에 프로젝트를 공유할 수 있는 시스템을 마련하려고 하는 차에 이 놈이 딱 눈에 들어오게 됐네요.
레일스로 만들어진 어플리케이션이라 조금 수정을 하면 다른 레일스 어플리케이션이랑 잘 붙을 것 같더군요.

우선 기능을 몇 개 살펴보면, 위에 언급한 위키, scm, 이슈 트래킹 시스템 등은 모두 사용할 수 있습니다.
더욱이 기본적으로 멀티 프로젝트가 가능하고 trac 에는 존재하지 않았었던 프로젝트 별 포럼과 파일 저장소를 지원합니다.
단순히 프로젝트 관리 기능에만 충실했던 trac 과는 달리 뉴스, 문서 관리 시스템 등 일종의 커뮤니티 및 배포 사이트로서의 기능이 포함되어 있습니다.

일단 한 번 설치를 해 봅시다.
우분투 8.10 데스크탑 버젼을 기준으로 설명합니다.

우선 subversion 클라이언트가 설치되어 있지 않으신 분은
sudo apt-get install subversion
으로 subversion 클라이언트를 준비합시다.

다음은 최신 stable 버젼을 저장소에서 export 합니다.
svn co http://redmine.rubyforge.org/svn/branches/0.7-stable redmine-0.7

디렉토리로 이동 후 webrick을 실행합니다.
cd redmine-0.7
./script/server

제 경우는 레일스를 2.1.0 버젼으로 업데이트 했는 데 config/boot.rb 가 버젼이 맞지 않는다고 하더군요.
redmine 은 레일스 2.0.2 버젼을 필요로 합니다.
rake rails:update

명령으로 boot.rb 를 최신 버젼으로 업데이트 시킬 수 있습니다.
레일스가 설치되지 않으신 분은
sudo gem install -v=2.0.2 rails
로 2.0.2의 버젼을 설치하시던가, 최신 버젼의 레일스를 설치하시고 저처럼 아래와 같은 작업을 하시면 됩니다.

config/environment.rb 를 열어서, 아래와 같은 부분에서 RAILS_GEM_VERSION을 현재 자신이 사용하고 있는 레일스의 버젼과 맞추어 줍시다.
RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION

다음은 데이터 베이스 설정입니다.
저는 mysql을 이용하므로 mysql 서버에 접속해 데이터 베이스를 만들어 줍니다.
 create database redmine_development default character set utf8;
 create database redmine_production default character set utf8;
 create database redmine_test default character set utf8;

사실 production 환경으로 돌리는 것이 가장 나을 거고 그러면 redmne_production 만 만들어 주면 됩니다.
기본으로는 development 환경으로 서버가 돌아가게 되는 데, config/environment.rb 등에서 RAILS_ENV 를 'production'으로 변경하면 production 환경으로 돌아가게 됩니다. 저는 다른 프로그램들과 연동시킬 생각이라 development 로 돌리기로 했습니다.

다음은 config/database.yml을 수정합니다.
cp config/database.yml.example config/database.yml

로 샘플 설정을 복사하시고,  db 이름, db user 이름, db user 패스워드 등을 잘 설정하시면 됩니다.
우분투의 경우 mysql 소켓의 위치가 rails 기본값과는 달라서 아래와 같이 해 주거나 혹은 mysql gem 을 설치하면 된다고 합니다.

development:
  adapter: mysql
  database: redmine_development
  host: localhost
  username: root
  password:
  encoding: utf8
  socket: /var/run/mysqld/mysqld.sock


./script/server

웹 서버를 돌려보려 하면, rfpdf 플러그인이 에러를 냅니다.(레일스 2.1 기준)

/home/alice/redmine/redmine-0.7/vendor/plugins/rfpdf/init.rb 의 소스 중
ActionView::Base::register_template_handler 'rfpdf', RFPDF::View

을 아래와 같이 수정합니다.

ActionView::Template::register_template_handler 'rfpdf', RFPDF::View

그래도 아직 에러를 내는군요.;;
app/controllers/application.rb 에 제일 첫째 줄에
require 'redmine'
이라고 적어줍시다.

그리고 rake!
rake db:migrate

rake 중에도 또 에러가 나는데..
db/migrate/072_add_enumerations_position.rb
db/migrate/078_add_custom_fields_position.rb
파일의 each_value 라는 메소드를 그냥 each로 바꿔 줍니다.

rake가 끝나면 마지막으로 webrick을 실행시켜 보기위해
./script/server
로 서버를 실행시킵니다.

그리고 localhost:3000 으로 확인해보면 끝!
redmine 의 시작 페이지

심플하다 못해 썰렁해서 왠지 모를 허무감마저 든다.


심플하다 못해 썰렁한 디자인의 redmine 첫 페이지가 나오네요.
이거 삽질하다 보니 그냥 레일스 2.0.2에다 설치하거나 redmine 개발 버젼을 설치할 걸 하는 후회도 좀 드네요. :)
그래도 굳이 난 레일스 2.1에 stable 버젼을 쓰겠다고 고집하시는 분이 계시면 이 글이 도움이 되었으면 하네요.
좀 사용해보고 trac과 비교도 해보고 하려고 했는데, 설치까지만 하도록 하고 다음 포스팅거리로 남겨둬야겠습니다.
사용기 전에 설치편 번외편;; 정도로 mod_rails, svn 연동을 다룰까합니다. mod_rails 설정이야 별 다를 것 없겠지만 이왕 하는 김에 같이 다룰까 합니다.
이올린에 북마크하기(0) 이올린에 추천하기(0)
top

POST : 프로그래밍/개발

레일스(Rails)의 함정

Rails 는 정말 훌륭한 웹 프레임워크입니다.
하지만 편하다고 아무렇게나 코딩을 하게 되면 골치아픈 상황들이 여럿 발생합니다.
프레임워크에서 어떤 SQL을 생성하는지, 각 쿼리들은 속도가 어떠한지 꾸준히 확인을 해 줘야합니다.

이 포스팅은 제가 정말 생각없이 코딩을 하게 되어서 겪은 여러가지 상황을 정리한 것입니다.

#1. N+1 문제

흔히 말하는 N+1 케이스입니다.

멀쩡해 보이는 코드지만.. 날아가는 쿼리가.. 대략.. 이렇습니다...-_-

Eager Loading 으로 해결가능 합니다만..
Eager Loading 으로 쿼리의 수는 1개로 확실히 적어지지만, 항상 성능이 좋아지는 것은 아닙니다.

자세한 건 이 글 을 참고하시면 좋겠습니다.

덧붙여서 윗글의 정보와는 달리 현재 Edgerails 에서는 :preload 옵션이 없고 :include 를 사용해도,
preload 가 가능한 경우는 Eager Loading이 아니라 Preload 를 하도록 패치되어 있습니다.


#2. Find conditions 에 시간을 사용할 경우는 한 번 더 생각을...
얼마전 테스트를 하다가 우연히 발견했습니다.

어느 쪽이 더 빠를까요? 당연히 테이블 전체를 가져오는 1번보다 2번이라고 생각하기 쉽습니다.
그런데 실제로 해보면 예상치 못한 결과가 나오더군요.

첫번째 호출할 때는 2번이 빠른데.. 두번째 이상 호출을 하게 될 경우, 1번이 훨씬 빠릅니다.
답은 각자 생성하는 쿼리에 있었습니다.

1의 경우는

로 처음이나 두번째 호출 후나 일정한 반면에..
2의 경우는

처럼 초단위가 변해서 쿼리가 날아갑니다.

한마디로 쿼리 캐시가 안되고 계속 새로운 쿼리를 보내는 것이죠.
아마 1초단위 까지 정확히 하루 전이라고 하고 싶으면 어쩔 수 없겠지만..
초단위 혹은 분단위를 올림/내림 등을 하는 것만으로도 쿼리 캐시의 효과를 잘 이용할 수 있게 될 겁니다.


#3. Pagination 의 함정.
이번엔 페이지를 나누는 Pagination 에서 생기는 문제입니다.
레일스 2.0 에서는 Pagination이 레일스 코어에서 빠지고 플러그인 식으로 설치를 해야 하는데..
제가 설치해 본 대부분의 Pagination 플러그인이 같은 문제를 가지고 있었습니다.

Pagination의 동작은 크게 두 부분으로 나눌 수 있습니다.
페이지 표시를 위해 전체 레코드 수를 가져오는 것과 페이지 번호를 이용해 :offset 과 :limit 으로 특정 부분의 레코드를 가져오는 부분입니다.
Pagination 의 플러그인 중 하나인 will_paginate 같은 경우 다음과 같이 Paginate를 합니다.


find 의 옵션들을 그대로 쓸 수 있으므로 아주 편하게 paginate를 할 수 있습니다.
그런데 문제는 include 옵션이 들어가게 되면 문제가 발생합니다.
우선은 paginate 는 #1 에서 언급한 preload 가 구현이 되어 있지 않고..
두번째 문제는 전체 레코드 수를 가져오는 부분에서 쓸 데없는 Join 이 일어납니다.
위의 경우는.

와 같은 쿼리가 생성됩니다. 그런데, 위의 경우는 굳이 comments와 join 할 필요가 없고,
만약 join을 해아할 테이블이 훨씬 많아지면 오버헤드는 상상할 수 없을 정도로 커집니다.
단순히 레코드가 몇 개인지 세는 것 뿐인데 말이죠.

외국의 글입니다. 이 paginate 문제 의 해결책을 제시하고 있습니다만 count 를 하는 쿼리와 선택을 하는 쿼리를 직접 SQL 로 만들어서 하는 방식은 가장 유연한 방식이지만 그다지 깔끔해 보이진 않네요.

will_paginate 의 이슈 트래커에 정확히 같은 문제를 지적한 티켓이 올라왔군요.



이올린에 북마크하기(0) 이올린에 추천하기(0)
top

tags

Rails, Ruby, Ruby on Rails, 개발, , 프로그래밍

posted at

2008/04/02 22:39


CONTENTS

Dragonkun in Tistory
BLOG main image

RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글 최근 트랙백
카테고리 태그 구름사이트 링크