본문으로 건너뛰기

Ruby on Rails와 함께하는 dhtmlxGantt

이 문서에서는 Ruby on Rails 백엔드를 사용하는 간트 차트 생성 방법을 배웁니다.
이 애플리케이션을 구현하기 위해 Ruby 2.4.1, Rails 5.1.3 및 MySQL을 사용할 예정입니다. 이 튜토리얼은 모든 전제 조건이 이미 설치되어 있다고 가정합니다.
그렇지 않으면 먼저 공식 튜토리얼을 방문해 보십시오.

다른 기술을 사용하는 경우 아래의 사용 가능한 통합 변형 목록을 확인하십시오:

GitHub의 데모도 살펴보세요.

Step 1. 프로젝트 생성

새 프로젝트를 추가하려면 터미널에서 다음 명령을 실행하세요:

rails new gantt-app -d mysql

Step 2. 페이지에 Gantt 추가하기

먼저 애플리케이션용 컨트롤러와 기본 페이지를 생성하는 것으로 시작합니다.
응용 프로그램 폴더로 이동하고 index 액션이 있는 새 컨트롤러를 생성합니다:

cd gantt-app
rails generate controller gantt index

출력 내용에 새 파일이 생성되었음을 확인하는 메시지가 표시되어야 합니다.

기본 라우트 설정

라우팅 구성을 위해 파일 config/routes.rb(를) 열고 기본 경로를 새 컨트롤러의 "index" 액션으로 변경합니다:

config/routes.rb
Rails.application.routes.draw do
root :to => "gantt#index"
end

그 후 명령줄에서 서버를 실행하여 테스트할 수 있습니다:

rails server

브라우저에서 http://localhost:3000/ 을 열면 다음과 같은 결과가 표시되어야 합니다:

how_to_start_rails_blank_page

따라서 앱이 작동하고 기본 페이지가 표시되며 이제 Gantt 차트를 추가할 수 있습니다.

View에 Gantt 추가하기

이제 페이지에 Gantt 차트를 추가할 준비가 되었습니다. 레이아웃 페이지를 열어 head 태그에 yield를 추가합니다. 이를 통해 페이지에 dhtmlxgantt 파일을 포함시킬 수 있습니다:

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>dhtmlxGantt</title>
(= stylesheet_link_tag 'application', media:'all','data-turbolinks-track' => true )
(= javascript_include_tag 'application', 'data-turbolinks-track' => true )
(= yield(:head) ) /*!*/
(= csrf_meta_tags )
</head>
<body>

(= yield )

</body>
</html>

그다음 gantt/index 뷰로 이동하여 거기에 Gantt 차트를 추가합니다:

app/views/gantt/index.html.erb
( content_for :head do )
(= stylesheet_link_tag 'https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css' )
(= javascript_include_tag 'https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.js' )
( end )

<div id="gantt_here" style='width:100%; height:800px;'></div>

<script>
gantt.init("gantt_here");
</script>

CDN에서 dhtmlxGantt 파일을 사용하도록 설정했습니다. 로컬에서 작업하려면 다운로드 패키지에 포함된 가독 가능한 버전의 소스 코드를 사용하는 것이 좋습니다.

그런 다음 현재 결과를 확인해 보겠습니다. 브라우저에서 http://localhost:3000/ 를 열면 데이터베이스의 작업과 링크로 채워진 간트 차트를 볼 수 있습니다. 다만 변경 사항은 아직 데이터베이스에 저장되지 않습니다. 다음 단계에서 저장 기능을 구현합니다.

how_to_start_rails_empty_gantt

이제 작업을 추가하고 수정할 수 있는 Gantt 차트를 얻었지만 저장 기능이 없습니다. 이를 제공하기 위해 모델을 생성해야 합니다.

Step 3. 모델 생성

현재 MySQL을 사용하므로 config/database.yml에 올바른 연결 설정이 있는지 확인합니다. 예시는 다음과 같습니다:

config/database.yml
development:
adapter: mysql2
encoding: utf8
host: localhost
database: gantt-app
username: root
password:

이제 TaskLink 모델을 생성해야 합니다.

Task 모델을 만들려면 작업 속성을 포함하는 명령을 실행합니다:

rails generate model Task 
text:string
start_date:datetime
duration:integer
parent:integer
progress:decimal

링크(Link) 모델은 비슷하지만 더 짧은 명령을 사용합니다:

rails generate model Link 
source:integer
target:integer
link_type:string:limit1

참고로 dhtmlxgantt 링크 객체에는 type이라는 속성이 필요합니다. 이는 관계의 유형(시작-시작, 종료-종료 등)을 저장합니다. 이 속성은 ActiveRecord에서 이미 예약되어 있기 때문에 모델에 추가할 수 없으므로, 이 속성의 이름은 link_type으로 지정하고 컨트롤러에서 필요한 매핑을 수행합니다.
Task 객체와 Link 객체에 대해 필수 및 선택 속성의 전체 목록은 각각 Task objectLink object에서 확인할 수 있습니다.

그다음 데이터베이스를 업데이트하기 위한 마이그레이션을 실행합니다:

rake db:migrate

여기에서 테스트 데이터를 몇 가지 추가해 보겠습니다:

  1. Rails 콘솔을 열려면 다음을 실행합니다:
rails c
  1. 다음과 같이 두 개의 작업(Task) 및 링크를 추가합니다:
Task.create :text=>"Task 1", :start_date=>"2015-10-25",  :duration=>2, :progress=>0;
Task.create :text=>"Task 2", :start_date=>"2015-10-27", :duration=>3, :progress=>0.5;
Link.create :source=>1, :target=>2, :link_type=>"0";
  1. 콘솔을 닫으려면 "exit"를 입력합니다.

다음으로 차트에서 데이터 로딩 및 저장을 컨트롤러의 도움으로 구현합니다.

Step 4. 데이터 로딩

모델 클래스를 생성하고 마이그레이션을 실행한 후 데이터베이스의 데이터를 gantt로 로드할 수 있습니다.
dhtmlxGantt는 JSON 형식의 데이터를 기대하므로 먼저 GanttController에 gantt 데이터를 읽고 형식화한 다음 출력하는 새 액션을 추가합니다:

app/controllers/gantt_controller.rb
class GanttController < ApplicationController
def index
end

def data
tasks = Task.all
links = Link.all

render :json=>{
:data => tasks.map{|task|{
:id => task.id,
:text => task.text,
:start_date => task.start_date.to_formatted_s(:db),
:duration => task.duration,
:progress => task.progress,
:parent => task.parent,
:open => true
}},
:links => links.map{|link|{
:id => link.id,
:source => link.source,
:target => link.target,
:type => link.link_type
}}
}
end
end

이 액션에 대한 라우트를 routes.rb에 추가합니다:

config/routes.rb
Rails.application.routes.draw do
root :to => "gantt#index"

scope '/api' do/*!*/
get "/data", :to => "gantt#data"/*!*/
end/*!*/
end

그리고 클라이언트 측에서 gantt.load 메서드를 사용해 이 액션을 호출합니다:

app/views/gantt/index.html.erb
gantt.config.date_format = "%Y-%m-%d %H:%i:%s";/*!*/

gantt.init("gantt_here");
gantt.load("/api/data");/*!*/

date_format 구성은 서버에서 오는 형식의 날짜들( Task의 start_date)을 지정합니다.
서버를 실행하고 브라우저에서 http://localhost:3000/ 을 열면 데이터베이스의 작업과 링크로 채워진 간트 차트를 볼 수 있습니다. 다만 변경 내용은 데이터베이스로 되돌려지지 않습니다. 다음 단계에서 이를 해결합니다.

Step 5. 변경 사항 저장

dhtmlxGantt는 사용자가 만든 모든 변경 사항을 백엔드의 RESTful API로 전송할 수 있으며, 그곳에서 데이터베이스에 저장될 수 있습니다. 프로토콜의 세부 정보는 여기를 확인하십시오.

이제 데이터 저장을 구현하는 방법은 다음과 같습니다:

먼저 클라이언트에서 변경 내용을 게시하도록 설정합니다:

app/views/gantt/index.html.erb
gantt.config.date_format = "%Y-%m-%d %H:%i:%s";

gantt.init("gantt_here");
gantt.load("/api/data");

var dp = new gantt.dataProcessor("/api");/*!*/
dp.init(gantt);/*!*/
dp.setTransactionMode("REST");/*!*/

그다음 Task용 컨트롤러와 Link용 컨트롤러 두 개를 추가하고 필요한 모든 액션을 구현합니다.

Task 컨트롤러 생성

Tasks용 컨트롤러부터 시작합니다:

rails generate controller task --no-helper --no-assets --no-view-specs

이 컨트롤러는 뷰를 가지지 않으므로 필요하지 않은 파일 생성을 방지하기 위해 --no- 플래그를 사용했습니다.

다음으로 작업 생성, 업데이트 및 삭제를 위한 액션을 구현합니다:

app/controllers/task_controller.rb
class TaskController < ApplicationController
protect_from_forgery

def update
task = Task.find(params["id"])
task.text = params["text"]
task.start_date = params["start_date"]
task.duration = params["duration"]
task.progress = params["progress"] || 0
task.parent = params["parent"]
task.save

render :json => {:action => "updated"}
end

def add
task = Task.create(
:text => params["text"],
:start_date=> params["start_date"],
:duration => params["duration"],
:progress => params["progress"] || 0,
:parent => params["parent"]
)

render :json => {:action => "inserted", :tid => task.id}
end

def delete
Task.find(params["id"]).destroy
render :json => {:action => "deleted"}
end
end

이 코드에 관한 몇 가지 주의사항:

  • 모든 데이터가 이미 gantt#data에서 로드되므로 get 액션은 필요하지 않습니다.
  • progress 속성은 클라이언트에서 기본적으로 초기화되지 않을 수 있으므로 여기에서 기본값을 제공해야 합니다. 필요하다면 모델 클래스에서 기본값을 정의할 수도 있습니다(예: migration).
  • 새 항목을 생성하는 액션은 새로 삽입된 레코드의 데이터베이스 ID를 클라이언트로 반환해야 합니다.

그런 다음 구성의 새 라우트를 추가하고 사용자가 간트 차트에서 작업을 조회/생성/수정/삭제할 수 있도록 합니다:

config/routes.rb
Rails.application.routes.draw do
root :to => "gantt#index"

scope '/api' do
get "/data", :to => "gantt#data"

post "/task", :to => "task#add"/*!*/
put "/task/:id", :to => "task#update"/*!*/
delete "/task/:id", :to => "task#delete"/*!*/
end
end

이제 Links에 대해서도 동일하게 구성합니다.

Link 컨트롤러를 생성합니다:

rails generate controller link --no-helper --no-assets --no-view-specs

구현 예시는 다음과 같을 수 있습니다:

app/controllers/link_controller.rb
class LinkController < ApplicationController
protect_from_forgery

def update
link = Link.find(params["id"])
link.source = params["source"]
link.target = params["target"]
link.link_type = params["type"]
link.save

render :json => {:action => "updated"}
end

def add
link = Link.create(
:source => params["source"],
:target => params["target"],
:link_type => params["type"]
)

render :json => {:action => "inserted", :tid => link.id}
end

def delete
Link.find(params["id"]).destroy
render :json => {:action => "deleted"}
end
end

그런 다음 새로운 액션에 대한 라우트를 추가합니다:

config/routes.rb
Rails.application.routes.draw do
root :to => "gantt#index"

scope '/api' do
get "/data", :to => "gantt#data"

post "/task", :to => "task#add"
put "/task/:id", :to => "task#update"
delete "/task/:id", :to => "task#delete"

post "/link", :to => "link#add"/*!*/
put "/link/:id", :to => "link#update"/*!*/
delete "/link/:id", :to => "link#delete"/*!*/
end
end

그게 전부입니다. 지금 애플리케이션을 실행하면 Rails와 MySQL 백엔드를 사용하는 인터랙티브한 간트 차트를 얻을 수 있습니다:

how_to_start_rails_complete

더 많은 기능에 대해서는 가이드 페이지를 확인해 보세요.

작업 순서 저장

클라이언트 측의 간트 차트는 드래그 앤 드롭으로 작업 재정렬을 지원합니다. 이 기능을 사용하면 데이터베이스에 이 순서를 저장해야 합니다.
여기에서 일반적인 설명을 확인할 수 있습니다.

이제 이 기능을 앱에 추가해 보겠습니다.

클라이언트에서 작업 재정렬 활성화

우선 사용자가 UI에서 작업 순서를 변경할 수 있도록 해야 합니다. Index 뷰를 열고 gantt 구성 설정을 업데이트합니다:

app/views/gantt/index.html.erb
gantt.config.order_branch = true;/*!*/
gantt.config.order_branch_free = true;/*!*/

gantt.init("gantt_here");

이 변경을 백엔드에도 반영해야 합니다. 순서 정보를 모델에 추가해야 하며 이름은 sortorder로 하겠습니다. 업데이트된 모델 선언은 다음과 같을 수 있습니다:

rails generate model Task 
text:string
start_date:datetime
duration:integer
parent:integer
progress:decimal
sortorder:integer /*!*/

또는 기존 모델에 새 속성을 추가할 수 있습니다:

  1. 마이그레이션 생성:
rails generate migration add_sortorder_to_tasks sortorder:integer
  1. 생성된 마이그레이션을 열고 "sortorder" 열에 기본값을 추가합니다:
class AddSortorderToTasks < ActiveRecord::Migration[5.1]
def change
add_column :tasks, :sortorder, :integer, :default=>0
end
end

그리고 마이그레이션을 실행합니다:

rake db:migrate

그다음 컨트롤러의 CRUD를 업데이트합니다:

  • data 액션은 sortorder 열로 정렬된 작업을 반환해야 합니다:
app/controllers/gantt_controller.rb
class GanttController < ApplicationController
def index
end

def data
tasks = Task.all
links = Link.all

render :json=>{
:data => tasks.order(:sortorder).map{|task|{ /*!*/
:id => task.id,
:text => task.text,
:start_date => task.start_date.to_formatted_s(:db),
:duration => task.duration,
:progress => task.progress,
:parent => task.parent,
:open => true
}},
:links => links.map{|link|{
:id => link.id,
:source => link.source,
:target => link.target,
:type => link.link_type
}}
}
end
end
  • 새로 추가된 작업은 초기 값으로 sortorder를 받아야 합니다:
app/controllers/task_controller.rb
class TaskController < ApplicationController
...
def add

maxOrder = Task.maximum("sortorder") || 0/*!*/

task = Task.create(
:text => params["text"],
:start_date=> params["start_date"],
:duration => params["duration"],
:progress => params["progress"] || 0,
:parent => params["parent"],
:sortorder => maxOrder + 1/*!*/
)

render :json => {:action => "inserted", :tid => task.id}
end

end
app/controllers/task_controller.rb
class TaskController < ApplicationController
protect_from_forgery

def update
task = Task.find(params["id"])
task.text = params["text"]
task.start_date = params["start_date"]
task.duration = params["duration"]
task.progress = params["progress"] || 0
task.parent = params["parent"]
task.save

if(params['target'])/*!*/
Task.updateOrder(task.id, params['target'])/*!*/
end/*!*/

render :json => {:action => "updated"}
end

...
end

Task.updateOrder 구현:

app/models/task.rb
class Task < ApplicationRecord
def self.updateOrder(taskId, target)
nextTask = false
targetId = target

if(target.start_with?('next:'))
targetId = target['next:'.length, target.length]
nextTask = true;
end

if(targetId == 'null')
return
end

targetTask = self.find(targetId)

targetOrder = targetTask.sortorder

if(nextTask)
targetOrder += 1
end

self.where("sortorder >= ?", targetOrder).
update_all('sortorder = sortorder + 1')

task = self.find(taskId)
task.sortorder = targetOrder
task.save
end
end

애플리케이션 보안

Gantt는 SQL 주입이나 XSS, CSRF 공격 등으로부터 애플리케이션을 안전하게 보호하는 수단을 제공하지 않습니다. 애플리케이션의 보안을 유지하는 책임은 백엔드를 구현하는 개발자에게 있습니다. 자세한 내용은 해당 문서를 참고하십시오.

문제 해결

위의 단계를 따라 Ruby on Rails와의 Gantt 통합을 구현했는데도 페이지에 작업과 링크가 렌더링되지 않는 경우에는 Backend 통합 문제 해결을 참조하시기 바랍니다. 문제의 원인을 식별하는 방법이 설명되어 있습니다.

What's next

이제 완전히 작동하는 간트 차트를 가지게 되었습니다. 전체 코드는 GitHub에서 확인하거나 복제(Clone) 또는 다운로드하여 프로젝트에 사용할 수 있습니다.

또한 가이드에서 간트의 다양한 기능을 확인하거나 다른 백엔드 프레임워크와의 Gantt 통합에 대한 튜토리얼을 확인해 보세요( integrations/howtostart-guides.md ).

Need help?
Got a question about the documentation? Reach out to our technical support team for help and guidance. For custom component solutions, visit the Services page.