[번역] 레일스의 그래프 그리기 실습: 차트킥 사례

1378086969294 Wagurano님이 약 일 년전에 작성함. 0 366 
레일스의 그래프 그리기 실습: 차트킥 사례 --------------------------------------- 원문: https://www.sitepoint.com/graphs-on-rails-chartkick-in-practice/ 일리야 보드로프-크루코브스키 2016년 8월 4일 씀 ![chart](//dab1nmslvvntp.cloudfront.net/wp-content/uploads/2016/08/1470252170site.png) [지난 글](https://www.sitepoint.com/make-easy-graphs-and-charts-on-rails-with-chartkick/)([번역](http://rorlab.org/rblogs/248))에서, 레일스 앱에서 그래프를 쉽게 그리는 최선의 젬, [차트킥](https://github.com/ankane/chartkick)의 기초 사용법을 다루었다. 관심을 받아서 차트킥의 기초를 조금 더 다루고 [그룹데이트](https://github.com/ankane/groupdate)와 함께 차트킥을 실제로 적용하는 방법을 살펴보기로 정했다. 들어가기에 앞서, 차트킥 젬은 레일스 프로젝트에 잘 어울리고 여러분의 데이타를 가지고 그래프를 빨리 그리는 메소드가 있다. 차트제이에스 Chart.js 구글 차트 Google Charts 하이차트 Highchart 어댑터를 지원한다. 차트킥 젬을 설치하면 그룹데이트 젬을 쓰는 것은 당연하고 그룹데이트 젬을 쓰면 그룹을 묶는 복잡한 쿼리를 쉽게 만들 수 있다. 여기서 여러분이 일자, 월, 연도에 따라 다양한 항목에 대한 클릭 카운트를 시각화하는 앱을 만드는 과정을 보여주겠다. 사용자는 날짜 범위를 선택할 수 있으며 간단한 일자 선택기가 도와주고 그래프의 구간을 일자 선택에 따라 알아서 조정한다. 이 소스코드는 [기트허브](https://github.com/bodrovis/Sitepoint-source/tree/master/Graphs_Chartkick_Practice)에서 찾을 수 있다. 데모는 [헤로쿠](https://sitepoint-chart-practice.herokuapp.com/)에서 찾을 수 있다. # 준비하기 이 데모에서 레일스5를 사용하겠지만, 레일스3와 4에서도 따라해도 거의 똑같다. 자, "항목들"의 수치를 보여주는 앱을 만드는 아이디어이며, 여기서 항목들의 종류는 중요하지 않지만, 온라인 상점에서 과일을 산다고 가정한다. 구매자의 관심사항은 과일 수확한 곳에 대한 클릭수이고, 클릭수 정보를 저장하여 시각 자료로 보고 싶어한다. 소비자는 "지난 6개월" 또는 "지난 해"와 같이 날짜 범위를 바꾸면서 각 항목마다 일자별 클릭수를 원한다. 자, 할일은 분면하고 이제로 코드로 들어가겠다. 테스트 코드를 만들지 않고 Tracker 라는 이름으로 레일스 앱을 새로 만든다: $ rails new Tracker -T 아래의 젬을 넣는다: Gemfile [...] gem 'chartkick' gem 'groupdate' gem 'bootstrap-sass' gem 'pg' [...] '[chartkick](https://github.com/ankane/chartkick)'은 여기서 당연히 중요한 툴이다. '[groupdate](https://github.com/ankane/groupdate)'는 어려운 쿼리를 쉽게 사용할 수 있다. '[bootstrap-sass](https://github.com/twbs/bootstrap-sass)'는 웹문서 스타일을 꾸미는데 사용하겠고 '[pg](https://bitbucket.org/ged/ruby-pg/wiki/Home)'는 PostgreSQL 어댑터이다. 그룹데이트는 'SQLite 3'에서 **동작하지 않으므로** 주의하라! 젬을 설치한다: $ bundle install Postgres 데이터베이스를 새로 만들고 앱에서 사용할 수 있도록 설정하는 것을 깜빡하지 말라. 아래의 설정 내용을 참고하여 시작해도 된다: config/database.yml development: adapter: postgresql encoding: unicode database: tracker pool: 5 username: "PG_USER" password: "PG_PASSWORD" host: localhost port: 5432 또한, 아래의 애셋을 추가하겠다: * [하이차트 자바스크립트 파일](https://code.highcharts.com/highcharts.js) * [부트스트랩 날짜선택기](https://github.com/eternicode/bootstrap-datepicker) [하이차트](http://www.highcharts.com/)는 사용자가 입력하는 것에 따라 모양이 바뀌는 그래프를 만드는데 매우 좋은 라이브러리여서 차트를 그리는 데모에 사용하겠다. 차트킥이 별다른 옵션없이 정해둔 [차트제이에스 Chart.js](https://github.com/ankane/chartkick#chartjs)나 [구글 차트](https://github.com/ankane/chartkick#google-charts)와 같은 다른 라이브러리를 계속 사용해도 된다. 부트스트랩 날짜선택기는 부트스트랩에 기능을 추가하는 애드온으로 입력 폼을 만드는데 쓴다. 자바스크립트 애셋을 모두 연결한다(터보링크는 마지막에 둬야 한다): javascripts/application.js //= require jquery //= require jquery_ujs //= require datepicker //= require highcharts //= require chartkick //= require turbolinks stylesheets/application.scss @import 'bootstrap-sprockets'; @import 'bootstrap'; @import 'datepicker'; 잘했다! 물론, 데이타와 데이타를 저장할 곳이 필요하여, 다음으로 넘어가서 모델을 만들자. # 모델 매우 간단한 모델 두개를 만든다: **아이템 Item**과 **클릭트랙 ClickTrack** $ rails g model Item title:string $ rails g model ClickTrack item:belongs_to $ rake db:migrate 아이템 Item은 상품이고 아이템을 클릭할 때마다 클릭 트랙 ClickTrack을 만든다. 이 로직을 코드로 쓰지 않지만, 아주 간단하다. 일대다 관계를 두 모델에 정의하는 법: models/item.rb class Item < ApplicationRecord has_many :click_tracks end models/click_track.rb class ClickTrack < ApplicationRecord belongs_to :item end 물론, 샘플 데이타가 필요하다. 여기서 그럴듯해 보이는 것은 하지 않고, 아이템 두개를 만들되 클릭 트랙을 만든 날짜가 겹치지 않도록 섞겠다: db/seeds.rb %w(apple cherry).each do |item| new_item = Item.create({title: item}) 1000.times do new_item.click_tracks.create!({created_at: rand(3.years.ago..Time.now) }) end end 샘플 데이타를 데이터베이스에 넣는다: $ rails db:seed # 레일스 5보다 이전 버전에서는 rails 대신 rake 로 실행한다 레일스 5 버전부터 모든 명령어는 rails 로 시작하는 것을 떠올려라: $ rails db:migrate # 레일스 5보다 이전 버전에서는 rails 대신 rake 로 실행한다 # 라우트와 메인 페이지 기본 라우트를 설정하고 앱의 홈페이지를 준비하자. 클릭 틀랙이 아이템에 묶여있으며 직접 클릭 트랙을 가져오지 않아도 된다면, click_track을 아래와 같이 리소스안에 리소스로 정의한다: config/routes.rb [...] resources :items, only: [:index] do resources :click_tracks, only: [:index] end root 'items#index' [...] 콘트롤러를 만든다: items_controller.rb class ItemsController < ApplicationController def index @items = Item.all end end click_tracks_controller.rb class ClickTracksController < ApplicationController def index @item = Item.find_by(id: params[:item_id]) end end 이 코드는 아주 기초적인 내용이라서 덧붙일 설명이 없다. 루트 페이지의 뷰는: views/items/index.html.erb <h1>Items</h1> <ul><%= render @items %></ul> 여기서 쓰는 파샬은 views/items/_item.html.erb <li> <%= item.title %> <%= link_to 'View clicks', item_click_tracks_path(item), class: 'btn btn-primary' %> </li> 라우트 안에 다른 라우트를 썼기때문에, click_tracks_path 헬퍼가 아니라 item_click_tracks_path 헬퍼를 써야 한다. [리소스 중첩](http://guides.rubyonrails.org/routing.html#nested-resources)에 대해 자세히 읽어도 좋다. 마지막에, 페이지 본문을 부트스트랩의 CSS 클래스를 쓴 div 태그로 감싸자: views/layouts/application.html.erb [...] <body> <div class="container"> <%= yield %> </div> </body> [...] # 입력 폼 만들기 자, 첫번째 단계는 끝내고 주요 기능을 만들 차례다. 입력 폼에 대해 말하자면, 입력할 것은 두개가 있어야 한다: 날짜의 시작과 끝이다. 시작과 끝 날짜를 두면, 사용자가 클릭 트랙을 보여줄 기간을 정할 수 있다. 날짜 입력은 부트스트랩의 CSS 스타일과 날짜선택기 플러그인으로 만든다: views/click_tracks/index.html.erb <h1>Click tracking</h1> <div id="event_period" class="row"> <%= form_tag api_item_click_tracks_path(@item), remote: true do %> <div class="col-sm-1"> <label for="start_date">Start date</label> </div> <div class="col-sm-3"> <div class="input-group"> <input type="text" class="actual_range form-control datepicker" id="start_date" name="start_date"> <div class="input-group-addon"> <span class="glyphicon glyphicon-th"></span> </div> </div> </div> <div class="col-sm-1 col-sm-offset-1"> <label for="end_date">End date</label> </div> <div class="col-sm-3"> <div class="input-group"> <input type="text" class="actual_range form-control datepicker" id="end_date" name="end_date"> <div class="input-group-addon"> <span class="glyphicon glyphicon-th"></span> </div> </div> </div> <div class="col-sm-2"> <%= submit_tag 'Show!', class: 'btn btn-primary' %> </div> <% end %> </div> 코드는 길이에 비해 간단하다. 입력 폼의 내용은 현재 웹페이지를 이동하지 않고 아직 라우드를 정의하지 않은 api_click_tracks_path로 보낸다. 입력 폼 안에 #start_date과 #end_date을 id로 날짜 입력 두개가 있다. 날짜 입력을 꾸미기 위해 .input-group-addon 클래스를 써서 옆에 작은 아이콘을 붙인다. 마지막에 입력 내용을 보내기 위한 제출 submit 버튼을 넣는다. 라우트는 아래와 같다: config/routes.rb [...] namespace :api do resources :items, only: [] do resources :click_tracks, only: [:create] do collection do get 'by_day' end end end end [...] api라는 네임스페이스 아래에서 라우트를 정의한다. 여기에 맞는 액션을 다음 과정에서 코딩하겠다. 날짜선택기 플러그인의 장점을 가지려면, 뷰 안에 다음과 같은 코드를 넣는다(물론, 커피스크립트 파일로 분리해서 넣을 수 있다): views/click_tracks/index.html.erb [...] <script data-turbolinks-track> $(document).ready(function() { $('#event_period').datepicker({ inputs: $('.actual_range'), startDate: '-3y', endDate: '0d', todayBtn: 'linked', todayHighlight: 'true', format: 'yyyy-mm-dd' }); }); </script> [...] 위의 코드로 새로운 기능을 가진 입력 폼을 모두 갖추며 실제로 쓰일만한 옵션을 제공한다. 또한, startDatd를 3년전으로 설정하고(앞서 클릭 트랙을 만든 날짜를 rand(3.years.ago..Time.now)와 같이 정의했기 때문에) endDate를 0d로 설정한 것은 오늘 날짜를 포함해야 하기 때문이다. 그런다음, "Today" 버턴과 오늘 날짜를 표시하여 날짜를 선택하는 포맷을 제공한다. 좋다! ![date_picker](//dab1nmslvvntp.cloudfront.net/wp-content/uploads/2016/08/1470251954date_picker.png) 서버를 실행하여 결과를 보라. 드롭-다운이 열리면, 연도나 월을 클릭하여 다른 날짜를 선택할 수 있다. # 그래프를 그리기 그럼, 오늘의 주인공이 나타날 차례다. 그래프는 파셜로 나눠서 그린다(그래야 마크업을 나중에 다시 사용할 수 있다): views/click_tracks/index.html.erb [...] <%= render 'graph' %> views/click_tracks/_graph.html.erb <div id="graph"> <%= stat_by(@start_date, @end_date) %> </div> stat_by는 앞으로 만들 헬퍼 메소드로 시작 날짜와 종료 날짜를 입력으로 받는다. 페이지를 모두 불러들이면, 시작과 종료 날짜는 아직 설정하지 않았으므로, 그러한 시나리오를 직접 처리해야 한다. helpers/click_tracks_helper.rb module ClickTracksHelper def stat_by(start_date, end_date) start_date ||= 1.month.ago end_date ||= Time.current end end 여기서 "닐 가드 nil guards"라고 불리는 (||=)를 써서 디폴트 값을 설정한다. 차트킥이 [비동기로 불러오는 기능](//github.com/ankane/chartkick#say-goodbye-to-timeouts)을 써서 차트를 그리자: helpers/click_tracks_helper.rb module ClickTracksHelper def stat_by(start_date, end_date) start_date ||= 1.month.ago end_date ||= Time.current line_chart by_day_api_item_click_tracks_path(@item, start_date: start_date, end_date: end_date), basic_opts('Click count', start_date, end_date) end end 따라서, 페이지를 불러오는 동안 차트를 가져오는 대신에, 사용자 경험에 있어서 더 좋게 백그라운드로 처리한다. 그러기 위해, 포맷이 올바른 데이타를 제공하는 액션을 구성하여 제이쿼리 jQuery나 젭토제이에스 Zepto.js와 연결해야 한다. 여기서는 by_day_api_item_click_tracks_path를 사용하지만, 아직 코딩하지 않았다. 이 액션은 다음 과정에서 코딩하겠다. basic_opts 메소드는 그 이름처럼, 특정 라이브러리를 포함하여 그래프 옵션 몇가지를 준비한다: helpers/click_tracks_helper.rb private def basic_opts(title, start_date, end_date) { discrete: true, library: { title: {text: title, x: -20}, subtitle: {text: "from #{l(start_date, format: :medium)} to #{l(end_date, format: :medium)}", x: -20}, yAxis: { title: { text: 'Count' } }, tooltip: { valueSuffix: 'click(s)' }, credits: { enabled: false } } } end l 메소드는 타임스탬프 포맷에 대한 로컬라이즈 메소드를 줄여서 쓴 것이다. :medium이라는 포맷은 없으므로 아래와 같이 새로 추가한다: config/locales/en.yml en: time: formats: medium: '%d %B %Y' 다시 강조하면, 그래프 어댑터마다 옵션이 다르다. 세부 내용은 어댑터 문서를 참고하라. # 콘트롤러 액션 프론트 엔드는 준비되었고 백엔드를 준비할 차례다. api 네임스페이스 아래에 라우트를 정의했기 때문에, 새로 만들 콘트롤러 파일은 api 폴더 안에서 만들어야 한다: controllers/api/click_tracks_controller.rb class Api::ClickTracksController < Api::BaseController end 그러나, Api::ClickTracksController는 Api::BaseController를 상속하고, 모든 Api::* 콘트롤러도 마찬가지다. 실례로, 프로덕션 앱에서 메소드를 공유하는 비슷한 콘트롤러가 두개 있다. controllers/api/base_controller.rb class Api::BaseController < ApplicationController end Api::BaseController 안에서 무슨 일이 일어나기를 원하는가? 콜백 두개를 쓴다: 첫번째 콜백은 아이템과 아이템의 클릭 틀랙 데이타를 불러오고 두번째 콜백은 입력으로 받은 날짜에 대한 포맷을 처리한다.(날짜를 따로 처리하는 이유는 사용자가 날짜선택기대신 직접 날짜를 입력하거나 시작과 종료 날짜를 입력하지 않고 입력 폼을 보낼 수 있기 떄문이다.) 데이타를 볼러오는 것은 문제가 아니다: controllers/api/base_controller.rb [...] before_action :load_data private def load_data @item = Item.includes(:click_tracks).find_by(id: params[:item_id]) @click_tracks = @item.click_tracks end [...] 시작과 종료 날짜 포맷을 처리하는 메소드는 좀 더 복잡하다: controllers/api/base_controller.rb [...] before_action :load_data before_action :format_dates private def format_dates @start_date = params[:start_date].nil? || params[:start_date].empty? ? 1.month.ago.midnight : params[:start_date].to_datetime.midnight @end_date = params[:end_date].nil? || params[:end_date].empty? ? Time.current.at_end_of_day : params[:end_date].to_datetime.at_end_of_day @start_date, @end_date = @end_date, @start_date if @end_date < @start_date end [...] 시작과 종료 날짜 중 하나가 없으면, 디폴트 값으로 채운다. midnight과 at_end_of_day 메소드를 사용하는 것이 그 자체로 얼마나 직관적인지 보라. 날짜가 있으면, 파라미터로 받은 날짜는 문자열이라서 데이트타임으로 변환한다. 마지막에, 시작 날짜보다 종료 날짜가 빠르면 시작과 종료 날짜를 서로 바꾼다. 입력 폼을 보낼 때 실행하는 create 액션을 코딩한다. 입력 폼을 AJAX로 보내기 때문에 당연히 자바스크립트 포맷으로 처리한다: controllers/api/click_tracks_controller.rb [...] def create respond_to do |format| format.js end end [...] 자바스크립트는 실제로 간단하다: 과거 차트를 새 차트로 바꾼다. 위에서 파샬로 만든 것을 불러온다: views/api/click_tracks/create.js.erb $('#graph').replaceWith('<%= j render 'click_tracks/graph' %>'); 마지막 단계는 그래프의 데이타를 준비하는 액션을 코딩한다. 위에서 봤듯이, 라우트는 다음과 같다: config/routes.rb [...] namespace :api do resources :items, only: [] do resources :click_tracks, only: [:create] do collection do get 'by_day' end end end end [...] 따라서, by_day 액션으로 호출해야 한다. 이 액션은 날짜의 클릭수에 대한 정보를 담는 JSON를 출력하려고 한다. controllers/api/click_tracks_controller.rb [...] def by_day clicks = @click_tracks.group_by_day('created_at', format: '%d %b', range: @start_date..@end_date).count render json: [{name: 'Click count', data: clicks}].chart_json end [...] group_by_day 메소드는 [그룹데이트 젬](https://github.com/ankane/groupdate#time-range)이 만들고 클릭 트랙을 만든 날짜별로 묶는다. count 메소드는, 이름에서 떠올리듯이, 날짜별로 클릭수를 센다. count 메소드 실행 결과 click 변수는 {'07 Jun': 10, '08 Jun': 4}와 같은 객체를 담을 것이다(여기서 키 포맷은 :format 옵션으로 어떻게 나타낼지 정한다). chart_json은 차트킥의 특별한 메소드로 데이타를 그래픽으로 표현할 JSON을 준비한다. 그러나, 사용자가 시간 범위를 2년간으로 선택하는 경우: 현재 메소드를 구현한 것으로 그래프 하나에 700일 이상을 그릴 것이다. 대신에, 범위를 확인하고 범위에 따라 그룹을 묶자. 우선, BaseController에 메소드를 두개 더 만든다: controllers/api/base_controller.rb [...] private def by_year? @end_date - (1.year + 2.days) > @start_date end def by_month? @end_date - (3.month + 2.days) > @start_date end [...] 이 메소드 두개는 선택한 기간의 길이를 간단히 확인하며 by_day 메소드에서 사용한다: controllers/api/click_tracks_controller.rb [...] def by_day opts = ['created_at', {range: @start_date..@end_date, format: '%d %b'}] method_name = :group_by_day if by_year? opts[1].merge!({format: '%Y'}) method_name = :group_by_year elsif by_month? opts[1].merge!({format: '%b %Y'}) method_name = :group_by_month end clicks = @click_tracks.send(method_name, *opts).count render json: [{name: 'Click count', data: clicks}].chart_json end [...] 여기서 그룹데이트 메소드(group_by_day, group_by_month, group_by_year) 셋 중 하나로 전달할 아규먼트 배열을 준비한다. 다음으로, 호출할 메소드를 설정하고 몇가지 확인한다. 범위가 한달을 넘는 경우, format 옵션을 연도로 하거나 월과 연도로 표시하도록 경우에 맞는 다른 메소드를 호출한다. *opts는 배열을 가지고 아규먼트 목록으로 바꿀 것이다. 이러한 코드로 여러분에게 맞는 조건과 그룹을 묶는 규칙을 쉽게 정의할 수 있다. 할일이 끝났고 최종 결과를 볼 수 있다! ![Final site](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2016/08/1470251960site.png) # 맺음 말 이 글에서 차트킥과 그룹데이트에 대해 계속 이어서 설명하고 실제 프로젝트에 사용하였다. 사용자 경험이 조금 더 편하도록, 부트스트랩의 날짜선택기 플러그인을 사용하였다. 여기있는 코드는 더 확장할 수 있다. 예를 들어, 각 항목의 클릭 수를 보여주고 싶다면 코딩하는 것은 쉽다. 다른 궁금한 것이 있다면, 연락바란다 - 여러분이 피드백을 보여주면 매우 기쁠 것이다. 언제나, 함께하여 감사하며 다시 보자! ![글쓴이](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2016/02/1455561538cr_thumb-96x96.jpg) 일리야 보드로프-크루코브스키 Ilya Bodrov-Krukowski [트위터](https://twitter.com/bodrovis) [구글플러스](https://plus.google.com/103641984440210150447) [페이스북](https://facebook.com/isbodrov) [링크드인](https://linkedin.com/in/bodrovis) [기트허브](https://github.com/bodrovis) 일리야 보드로프는 전문 IT 강사이며, 캠페이너 LLC에서 시니어 엔지니어이고, 사이트포인트에서 글을 쓰며 가리치는 일을 돕고, 모스크바 항공 대학 Moscow Aviations Institute 로 강의 나간다. 주로 사용하는 프로그래밍 언어는 루비 (레일스 포함)와 자바스크립트다. 코딩하고, 사람들을 가리치고, 새로운 것을 배우기를 즐긴다. 일리야는 시스코와 마이크로소프트 자격증을 가지고 있고 교육 센터에서 강사로 일했었다. 시간이 남을 때 트위터를 하고, 자신의 [웹사이트](http://www.ilyabodrov.me/)에 글을 올리고, 오픈소스 프로젝트에 참여하고, 스포츠 하러 나가고 음악을 연주한다. 우리말 옮김: 와그라노(wagurano)

Comments (0)