<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[AlohaCC]]></title>
  <link href="http://ccaloha.cc/atom.xml" rel="self"/>
  <link href="http://ccaloha.cc/"/>
  <updated>2016-11-30T15:57:41+08:00</updated>
  <id>http://ccaloha.cc/</id>
  <author>
    <name><![CDATA[Aloha]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[Stress Test Using JMeter in Ruby - Part 2]]></title>
    <link href="http://ccaloha.cc/blog/2016/11/30/stress-test-using-jmeter-in-ruby-part-2/"/>
    <updated>2016-11-30T15:46:46+08:00</updated>
    <id>http://ccaloha.cc/blog/2016/11/30/stress-test-using-jmeter-in-ruby-part-2</id>
    <content type="html"><![CDATA[<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/jmeter/intro.png" alt="JMeter" /></p>

<blockquote><p>Due to update-to-date JMeter 3.0 require Java 7, but for current offical Java support in Mac OSX is 6. So, before running new JMeter you might need to upgrade your Java. Please check my <a href="https://gist.github.com/alChaCC/ddb11542c9e6b6683bad80d9ca858bc5">gist</a> for installation Java 7.</p></blockquote>

<p>This article just show you code example of how to use loop number and thread number in gem <code>'ruby-jmeter'</code>.</p>

<!--more-->


<p>Currently, we need to do stress test on your API service.</p>

<p>That&#8217;s see how to write codes in different scenarios.</p>

<p>Reference: <a href="http://jmeter.apache.org/usermanual/functions.html">http://jmeter.apache.org/usermanual/functions.html</a></p>

<h2>[Scenario 1] You want to change request data based on <code>thread number</code> </h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>test do
</span><span class='line'>  defaults domain: 'YOUR.API.ENDPOINT', portocal: 'https' , port: '443'
</span><span class='line'>  
</span><span class='line'>  header [
</span><span class='line'>    { name: 'Authorization', value: "Bearer 123456789" },
</span><span class='line'>    { name: 'Content-Type', value: 'application/json' }
</span><span class='line'>  ]
</span><span class='line'>
</span><span class='line'>  post_body = {
</span><span class='line'>    "order_number": "thread-${__threadNum}"
</span><span class='line'>  }
</span><span class='line'> 
</span><span class='line'>  threads count: 1000, rampup: 1 ,loops: 10, scheduler: false do
</span><span class='line'>      transaction "Shipments" do
</span><span class='line'>        post name: 'Create',
</span><span class='line'>             url: 'https://YOUR.API.ENDPOINT/v1/orders',
</span><span class='line'>             raw_body: post_body.to_json do
</span><span class='line'>               with_xhr
</span><span class='line'>        end
</span><span class='line'>      end
</span><span class='line'>    view_results_tree
</span><span class='line'>  end
</span><span class='line'>
</span><span class='line'>  view_results_in_table
</span><span class='line'>  graph_results
</span><span class='line'>  aggregate_graph
</span><span class='line'>  view_results_tree
</span><span class='line'>  summary_report
</span><span class='line'>end.run(
</span><span class='line'>  gui: true,
</span><span class='line'>  file: 'jmeter-order-api.jmx',
</span><span class='line'>  log:  'jmeter-order-api.log',
</span><span class='line'>  jtl:  'results-order-api.jtl'
</span><span class='line'>)</span></code></pre></td></tr></table></div></figure>


<h2>[Scenario 2] You want to change request data based on <code>thread number</code> and <code>iteration number</code> </h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>test do
</span><span class='line'>  ...
</span><span class='line'>  
</span><span class='line'>  post_body = {
</span><span class='line'>    "order_number": "thread-${__threadNum}-loop-${__BeanShell(vars.getIteration();,)}"
</span><span class='line'>  }
</span><span class='line'>  
</span><span class='line'>  ...</span></code></pre></td></tr></table></div></figure>


<h2>[Scenario 3] You already have data array and you want to use the array element as request data and use <code>iteration number</code> as index to get data</h2>

<blockquote><p>use <code>variables</code> to set variable array</p>

<p>variables accept <code>[{name: XXX, value: XXX}, {name: XXX, value: XXX}...]</code></p></blockquote>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>require 'pg'
</span><span class='line'>
</span><span class='line'>test do
</span><span class='line'>  ...
</span><span class='line'>  
</span><span class='line'>  conn = PG.connect(dbname:   'DATABASE_STAGING_DBNAME',
</span><span class='line'>                    host:     'DATABASE_STAGING_HOST',
</span><span class='line'>                    user:     'DATABASE_STAGING_USER',
</span><span class='line'>                    password: 'DATABASE_STAGING_PWD'
</span><span class='line'>                   )
</span><span class='line'>  orders = []
</span><span class='line'>  conn.exec( "SELECT * FROM orders where order_state = 'ready'" ) do |result|
</span><span class='line'>    result.each do |row|
</span><span class='line'>      orders &lt;&lt; row
</span><span class='line'>    end
</span><span class='line'>  end
</span><span class='line'>
</span><span class='line'>  order_index = 0
</span><span class='line'>  post_body_array = orders.inject([]) do |arr, order|
</span><span class='line'>    arr &lt;&lt; {
</span><span class='line'>      name: order_index.to_s,
</span><span class='line'>      value: {
</span><span class='line'>        orders: [
</span><span class='line'>          {
</span><span class='line'>            id: order['id'],
</span><span class='line'>            order_state: 'shipped'
</span><span class='line'>          }
</span><span class='line'>        ]
</span><span class='line'>      }.to_json
</span><span class='line'>    }
</span><span class='line'>    order_index += 1
</span><span class='line'>    arr
</span><span class='line'>  end
</span><span class='line'>
</span><span class='line'>  variables post_body_array
</span><span class='line'>
</span><span class='line'>  threads count: 1, rampup: 1, loops: order_index do
</span><span class='line'>    transaction "Update Order" do
</span><span class='line'>        post name: 'Update',
</span><span class='line'>             url: 'https://YOUR.API.ENDPOINT/v1/orders/update_url',
</span><span class='line'>             raw_body: '${__BeanShell(vars.get("${__BeanShell(vars.getIteration();,)}"))}' do
</span><span class='line'>               with_xhr
</span><span class='line'>             end
</span><span class='line'>    end
</span><span class='line'>    view_results_tree
</span><span class='line'>    debug_sampler
</span><span class='line'>  end
</span><span class='line'>  
</span><span class='line'>  ...</span></code></pre></td></tr></table></div></figure>


<h2>[Scenario 4] You already have data array and you want to use the array element as request data and use <code>thread number</code> as index to get data</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>...
</span><span class='line'>
</span><span class='line'>  threads count: order_index, rampup: 1 ,loops: 1 do
</span><span class='line'>    transaction "Update Order" do
</span><span class='line'>        post name: 'Update',
</span><span class='line'>             url: 'https://YOUR.API.ENDPOINT/v1/orders/update_url',
</span><span class='line'>             raw_body: '${__BeanShell(vars.get("${__threadNum}"))}' do
</span><span class='line'>               with_xhr
</span><span class='line'>             end
</span><span class='line'>    end
</span><span class='line'>    view_results_tree
</span><span class='line'>    debug_sampler
</span><span class='line'>  end
</span><span class='line'>  
</span><span class='line'>  ...</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[API Request Limitation Using Rack-Throttle and Doorkeeper]]></title>
    <link href="http://ccaloha.cc/blog/2016/11/24/api-request-limitation-using-rack-throttle-and-doorkeeper/"/>
    <updated>2016-11-24T12:52:06+08:00</updated>
    <id>http://ccaloha.cc/blog/2016/11/24/api-request-limitation-using-rack-throttle-and-doorkeeper</id>
    <content type="html"><![CDATA[<p>When it comes to open API to external users, we need to consider the number of requests we allowed. Most of API-as-a-service platforms have different payment plans. Due to different levels of payment, it can make sure that our service resource can be used fairly - advanced plan users can make more requests than normal users.</p>

<p>In order to achieve this goal, we need to find a way recording the number of requests. Intuitively, we can creata a <code>request_record</code> table, before controller process the request, by comparing the number in database and client&#8217;s request limitation, we can either allow or deny the requests. <br/>
However, we don&#8217;t need to build like this way.</p>

<p>Thanks to Ruby gems, you are able to find some libraries can do the trick. In this article,
I choose <code>rack-throttle</code> to help me count the requests. The reasons I prefer
<code>rack-throttle</code> are:</p>

<ol>
<li>To limit by defining a maximum number of allowed HTTP requests per a given time period</li>
<li>Compatible with any Rack application and any Rack-based framework.</li>
<li>Compatible with the memcached, memcache-client, memcache and redis gems.</li>
<li>Well documented and the most important thing is that codes are easy to read!</li>
</ol>


<!--more-->


<p>Different from recording, we still need to identify which client makes this request for deciding to allow or not. Here I use <code>Doorkeeper</code> to tell me which user is.</p>

<p>In this article, I will not show you how to setup <code>Doorkeeper</code>, but you are about to see how to customize your own limiter.</p>

<p>So, That&#8217;s go!</p>

<h2>Requirements</h2>

<p>My targets show as below:</p>

<ol>
<li>Different clients has its limitation.</li>
<li>Should limit by second, minute, hour, day and month.</li>
<li>Support whitelist</li>
</ol>


<h2>Gemfile</h2>

<p>At first, we need to add libraries, the most important gem is <code>rack-throttle</code>. Depend on which places you want to store your counting data, you might need to add other gems. For me, I prefer <code>redis</code>. So, I added as below.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># Api limit
</span><span class='line'>gem 'rack-throttle'
</span><span class='line'>gem 'redis'</span></code></pre></td></tr></table></div></figure>


<h2>Customize your throttler</h2>

<p>Wait wait, before we start implementing, we should understand some mechnisms and terms.</p>

<h3>First, <code>rack</code>!</h3>

<p>you might notice that the gem we use called <code>rack-throttle</code>. Yes! this lib is built as a <code>Rack middleware</code> that provides logic for rate-limiting incoming HTTP requests to Rack applications.</p>

<p>Ok, so what&#8217;s <code>Rack</code> ?</p>

<p>according to <a href="http://rack.github.io/">rack official website</a>,</p>

<blockquote><p>Rack provides a minimal interface between webservers that support Ruby and Ruby frameworks.</p>

<p>To use Rack, provide an <code>app</code>: an object that responds to the <code>call</code> method, taking the environment hash as a parameter, and returning an Array with three elements:</p>

<p>The HTTP response code</p>

<p>A Hash of headers</p>

<p>The response body, which must respond to each</p></blockquote>

<p>So, <code>Rack</code> can help you handle the HTTP request. And due to <code>Rack</code> take <code>the environment hash</code> and also can &#8220;return&#8221; fix format: <code>array with code, headers and body</code>. It&#8217;s very easy for you to add <code>middleware</code> which can help you do cacheing, authencation, &#8230;etc. In our case, gem <code>rack-throttle</code> will help you adding middleware easily.</p>

<p>After we briefly understand how <code>Rack</code> works, now let&#8217;s take a look how <code>rack-throttle</code>&#8217;s core class: <a href="https://github.com/dryruby/rack-throttle/blob/master/lib/rack/throttle/limiter.rb"><code>limiter</code></a>.</p>

<p>Why it work as <code>Rack middilewate</code>?</p>

<p>The key point is:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>def initialize(app, options = {})
</span><span class='line'>  @app, @options = app, options
</span><span class='line'>end
</span><span class='line'>
</span><span class='line'>def call(env)
</span><span class='line'>  request = Rack::Request.new(env)
</span><span class='line'>  allowed?(request) ? app.call(env) : rate_limit_exceeded(request)
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<p>So, after we add this middleware into our Rack, every time we receive the requests it will be invoked by <code>call</code> method. Then we check is allowed or not, if is allowed, we pass to next Rack middleware, otherwise we stop in this middleware.</p>

<h3>Since I need to customize the throttler, I can inherit from <code>Rack::Throttle::Limiter</code>.</h3>

<p>Let&#8217;s check the final code first.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
<span class='line-number'>55</span>
<span class='line-number'>56</span>
<span class='line-number'>57</span>
<span class='line-number'>58</span>
<span class='line-number'>59</span>
<span class='line-number'>60</span>
<span class='line-number'>61</span>
<span class='line-number'>62</span>
<span class='line-number'>63</span>
<span class='line-number'>64</span>
<span class='line-number'>65</span>
<span class='line-number'>66</span>
<span class='line-number'>67</span>
<span class='line-number'>68</span>
<span class='line-number'>69</span>
<span class='line-number'>70</span>
<span class='line-number'>71</span>
<span class='line-number'>72</span>
<span class='line-number'>73</span>
<span class='line-number'>74</span>
<span class='line-number'>75</span>
<span class='line-number'>76</span>
<span class='line-number'>77</span>
<span class='line-number'>78</span>
<span class='line-number'>79</span>
<span class='line-number'>80</span>
<span class='line-number'>81</span>
<span class='line-number'>82</span>
<span class='line-number'>83</span>
<span class='line-number'>84</span>
<span class='line-number'>85</span>
<span class='line-number'>86</span>
<span class='line-number'>87</span>
<span class='line-number'>88</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># lib/api_limitation/application_throttler.rb
</span><span class='line'>
</span><span class='line'>require 'rack/throttle'
</span><span class='line'>module ApiLimitation
</span><span class='line'>  class ApplicationThrottler &lt; Rack::Throttle::Limiter
</span><span class='line'>    attr_accessor :client, :ip
</span><span class='line'>
</span><span class='line'>    def initialize(app, options = {})
</span><span class='line'>      super
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def client_identifier(request)
</span><span class='line'>      if request.env['HTTP_AUTHORIZATION'] || request.params['access_token']
</span><span class='line'>        token        = request.env['HTTP_AUTHORIZATION'].present? ? request.env['HTTP_AUTHORIZATION'].split(' ')[-1] : request.params['access_token']
</span><span class='line'>        access_token = Doorkeeper::AccessToken.where(token: token).first
</span><span class='line'>        @client = access_token ? User.find(access_token.resource_owner_id) : nil
</span><span class='line'>      end
</span><span class='line'>      @ip = request.ip.to_s
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def max_per_second
</span><span class='line'>      @client.try(:limitation).try(:second) || 1
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def max_per_minute
</span><span class='line'>      @client.try(:limitation).try(:minute) || 60
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def max_per_hourly
</span><span class='line'>      @client.try(:limitation).try(:hourly) || 3_600
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def max_per_daily
</span><span class='line'>      @client.try(:limitation).try(:daily) || 86_400
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def max_per_monthly
</span><span class='line'>      @client.try(:limitation).try(:monthly) || 2_592_000
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def allowed?(request)
</span><span class='line'>      client_identifier(request)
</span><span class='line'>      return true if whitelisted?(request)
</span><span class='line'>
</span><span class='line'>      ['second', 'minute', 'hourly', 'daily', 'monthly'].all? { |timeslot| send("#{timeslot}_check".to_sym) }
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def whitelisted?(request)
</span><span class='line'>      @client.try(:email).in? ['YOUR_WHITE_LIST']
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    protected
</span><span class='line'>
</span><span class='line'>    ['second', 'minute', 'hourly', 'daily', 'monthly'].each do |timeslot|
</span><span class='line'>      define_method("#{timeslot}_check".to_sym) do
</span><span class='line'>        count = cache_get(key = send("#{timeslot}_cache_key".to_sym)).to_i + 1 rescue 1
</span><span class='line'>        allowed = count &lt;= send("max_per_#{timeslot}".to_sym).to_i
</span><span class='line'>        begin
</span><span class='line'>          cache_set(key, count)
</span><span class='line'>          allowed
</span><span class='line'>        rescue =&gt; e
</span><span class='line'>          allowed = true
</span><span class='line'>        end
</span><span class='line'>      end
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def second_cache_key
</span><span class='line'>      [@client.try(:id) || @ip, Time.now.strftime('%Y-%m-%dT%H:%M:%S')].join(':')
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def minute_cache_key
</span><span class='line'>      [@client.try(:id) || @ip, Time.now.strftime('%Y-%m-%dT%H:%M')].join(':')
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def hourly_cache_key
</span><span class='line'>      [@client.try(:id) || @ip, Time.now.strftime('%Y-%m-%dT%H')].join(':')
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def daily_cache_key
</span><span class='line'>      [@client.try(:id) || @ip, Time.now.strftime('%Y-%m-%d')].join(':')
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>    def monthly_cache_key
</span><span class='line'>      [@client.try(:id) || @ip, Time.now.strftime('%Y-%m')].join(':')
</span><span class='line'>    end
</span><span class='line'>
</span><span class='line'>  end
</span><span class='line'>end
</span></code></pre></td></tr></table></div></figure>


<p>The most important function is <code>allowed?(request)</code>.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>def allowed?(request)
</span><span class='line'>    client_identifier(request)
</span><span class='line'>    return true if whitelisted?(request)
</span><span class='line'>
</span><span class='line'>    ['second', 'minute', 'hourly', 'daily', 'monthly'].all? { |timeslot| send("#{timeslot}_check".to_sym) }
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<p>Let&#8217;s check each functions.</p>

<h4>First, <code>client_identifier(request)</code>.</h4>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>def client_identifier(request)
</span><span class='line'>    if request.env['HTTP_AUTHORIZATION'] || request.params['access_token']
</span><span class='line'>   token        = request.env['HTTP_AUTHORIZATION'].present? ? request.env['HTTP_AUTHORIZATION'].split(' ')[-1] : request.params['access_token']
</span><span class='line'>   access_token = Doorkeeper::AccessToken.where(token: token).first
</span><span class='line'>   @client = access_token ? User.find(access_token.resource_owner_id) : nil
</span><span class='line'>      end
</span><span class='line'>   @ip = request.ip.to_s
</span><span class='line'> end</span></code></pre></td></tr></table></div></figure>


<p>We used <code>doorkeeper</code> for Oauth2 authentication.</p>

<p>So, each requests might have <code>HTTP_AUTHORIZATION</code> in header or <code>access_token</code> in url. Then we need to use the <code>access_token</code> to find who he is.</p>

<h4>Second, <code>whitelisted?(request)</code></h4>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>def whitelisted?(request)
</span><span class='line'>  @client.try(:email).in? ['YOUR_WHITE_LIST']
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<p>Simple! Right?, because we already have @client from <code>client_identifier(request)</code>. I just try to find its email in list or not.</p>

<h4>Finall part, <code>['second', 'minute', 'hourly', 'daily', 'monthly'].all? { |timeslot| send("#{timeslot}_check".to_sym) }</code></h4>

<p>here I use <a href="https://www.toptal.com/ruby/ruby-metaprogramming-cooler-than-it-sounds"><code>metaprogramming</code></a> to avoid writing too many duplicate codes which call these functions: <code>second_check</code>, <code>minute_check</code>, &#8230;etc.</p>

<p>And you can&#8217;t find these functions in codes, which is because I also use metaprogramming method to implement class&#8217;s instance methods.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>['second', 'minute', 'hourly', 'daily', 'monthly'].each do |timeslot|
</span><span class='line'>  define_method("#{timeslot}_check".to_sym) do
</span><span class='line'>    count = cache_get(key = send("#{timeslot}_cache_key".to_sym)).to_i + 1 rescue 1
</span><span class='line'>    allowed = count &lt;= send("max_per_#{timeslot}".to_sym).to_i
</span><span class='line'>    begin
</span><span class='line'>      cache_set(key, count)
</span><span class='line'>      allowed
</span><span class='line'>    rescue =&gt; e
</span><span class='line'>      allowed = true
</span><span class='line'>    end
</span><span class='line'>  end
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<p>Ok, so here I try to get count number from cache(for me, I used Redis) by <code>cache_get</code> which is in base class: <code>Rack::Throttle::Limiter</code>. And the cache keys are either client&#8217;s id or ip with timestamp. for example: <code>12345:2016-11-24</code> or <code>127.0.0.1:2016-11-24</code>.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>def second_cache_key
</span><span class='line'>  [@client.try(:id) || @ip, Time.now.strftime('%Y-%m-%dT%H:%M:%S')].join(':')
</span><span class='line'>end
</span><span class='line'>...</span></code></pre></td></tr></table></div></figure>


<p>After we get the number of count we can compare it  to client&#8217;s limitation and add counting number as well.</p>

<p>And how do we get client&#8217;s limitation?</p>

<p>In our database design, user has one <code>limitation</code> which include these columns: <code>second</code>, <code>minute</code>&#8230;etc.
So, I can easily get it by <code>@client.limitation.XXX</code></p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>def max_per_second
</span><span class='line'>  @client.try(:limitation).try(:second) || 1
</span><span class='line'>end
</span><span class='line'>....</span></code></pre></td></tr></table></div></figure>


<p>That&#8217;s it. it&#8217;s not too hard right?</p>

<h2>Add your throttler into Rack.</h2>

<p>This part is super easy, just follow <a href="https://github.com/dryruby/rack-throttle"><code>rack-throttle</code>&#8217;s README</a>.</p>

<p>because I built it for Rails application.</p>

<p>I need to add this line into <code>config/application.rb</code></p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>module YOUR_AWESOME_APPLICATION
</span><span class='line'>  class Application &lt; Rails::Application
</span><span class='line'>   ...
</span><span class='line'>    config.middleware.use ::ApiLimitation::ApplicationThrottler, cache: Redis.new(host: YOUR_REDIS_HOST), key_prefix: :awesome_throttle, message: 'Rate Limit Exceeded, please upgrade your plan to get more requests'
</span><span class='line'>  end
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<p>Do you remember that I mentioned I use <code>Redis</code> for caching count? Here is the magic.</p>

<p>I initialize <code>@cache</code> in here. So that it can be accessed in class.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Split Single Lossless Audio(.ape, .flac) Files into mp3 in MacOSX]]></title>
    <link href="http://ccaloha.cc/blog/2016/06/25/split-single-lossless-audio-dot-ape/"/>
    <updated>2016-06-25T17:33:14+08:00</updated>
    <id>http://ccaloha.cc/blog/2016/06/25/split-single-lossless-audio-dot-ape</id>
    <content type="html"><![CDATA[<p>I got a lossless audio from my friend which included Taiwan famous folk songs in 1970~1979.</p>

<p>So, I want to split this audio into seperate mp3. Spending few hours, I found this library!</p>

<p>I have to thank @ftrvxmtrx - the author of <a href="https://github.com/ftrvxmtrx/split2flac">https://github.com/ftrvxmtrx/split2flac</a>.</p>

<p>In this post, I just show to how to setup the environment to use ftrvxmtrx&#8217;s libarary in MacOSX.</p>

<h2>1. Make sure you have homebrew</h2>

<h2>2. Install required/optional libs</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>brew install cuetools
</span><span class='line'>brew install shntool
</span><span class='line'>brew install id3lib
</span><span class='line'>brew install flac
</span><span class='line'>brew install homebrew/dupes/libiconv
</span><span class='line'>brew install enca
</span><span class='line'># for install mac to split APE
</span><span class='line'>brew tap fernandotcl/homebrew-fernandotcl
</span><span class='line'>brew install monkeys-audio</span></code></pre></td></tr></table></div></figure>


<h2>3. Clone project</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>git clone https://github.com/ftrvxmtrx/split2flac.git</span></code></pre></td></tr></table></div></figure>


<h2>4. Convert files</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>cd split2flac</span></code></pre></td></tr></table></div></figure>


<p>Check commands</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>./split2flac -h</span></code></pre></td></tr></table></div></figure>


<p>Convert the file</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>./split2flac -f mp3 -o ~/Desktop/ -cue path/to/your/cue-file.cue path/to/your/ape-file.ape</span></code></pre></td></tr></table></div></figure>


<p><strong>-f</strong>: using mp3 as output format</p>

<p><strong>-o</strong>: where to put the output files</p>

<p><strong>cue</strong>: file location to your .cue</p>

<p>yay!</p>

<p><img src='https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/split_lossless_audio/demo-%20split%20ape%20file%20into%20mp3.png' alt='demo: split ape file into mp3'></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[How to install Preloader.io service in Ubuntu 14.04]]></title>
    <link href="http://ccaloha.cc/blog/2016/06/22/how-to-install-preloader-dot-io-service-in-ubuntu-14-dot-04/"/>
    <updated>2016-06-22T17:58:14+08:00</updated>
    <id>http://ccaloha.cc/blog/2016/06/22/how-to-install-preloader-dot-io-service-in-ubuntu-14-dot-04</id>
    <content type="html"><![CDATA[<p>If you&#8217;re finding a solution for SEO especially for AngularJS, BackboneJS, EmberJS and other javascript frameworks. <code>Preloader</code> will be one of your options.</p>

<blockquote><p><code>Preloader</code> is a node server that uses phantomjs to create static HTML out of a javascript page.</p></blockquote>

<p>you are able to use their <a href="https://prerender.io/">solutions</a> instead of installing in your own machine.</p>

<p>But, what if you just like me, want to try their service first and want to customize your own Preloader?</p>

<p>here are flows that show how I install Preloader in Ubuntu 14.04.</p>

<!--more-->


<h2>1. Create an instance in Amazon Web Service</h2>

<p>hmmm, just go to AWS and create an instance using Ubuntu 14.04 image.</p>

<p>make sure you allow port <code>3000</code> can be access</p>

<h2>2. Create a user</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>sudo adduser deploy
</span><span class='line'>sudo adduser deploy sudo
</span><span class='line'>su deploy</span></code></pre></td></tr></table></div></figure>


<h2>3. Make sure you can login using new user without pem key</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>cd ~
</span><span class='line'>mkdir .ssh
</span><span class='line'>cd .ssh
</span><span class='line'>vim authorized_keys</span></code></pre></td></tr></table></div></figure>


<p>then put the content in <code>~/.ssh/id_rsa.pub</code> in your <strong>local machine</strong> into it.</p>

<h2>4. Install all services</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>sudo locale-gen UTF-8
</span><span class='line'>sudo apt-get update
</span><span class='line'>
</span><span class='line'># Install git
</span><span class='line'>sudo apt-get install git
</span><span class='line'>
</span><span class='line'># Install phantomjs
</span><span class='line'>sudo apt-get install phantomjs
</span><span class='line'>
</span><span class='line'># Install Node.js v6
</span><span class='line'>curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
</span><span class='line'>sudo apt-get install -y nodejs
</span><span class='line'>
</span><span class='line'># Clone the project
</span><span class='line'>git clone https://github.com/prerender/prerender.git
</span><span class='line'>cd prerender
</span><span class='line'>npm install
</span><span class='line'>
</span><span class='line'># Fix: Fontconfig warning: ignoring UTF-8: not a valid region tag
</span><span class='line'>export LANGUAGE=en_US.UTF-8
</span><span class='line'>export LANG=en_US.UTF-8
</span><span class='line'>export LC_ALL=en_US.UTF-8
</span><span class='line'>locale-gen en_US.UTF-8
</span><span class='line'>
</span><span class='line'># Run Node server
</span><span class='line'>node server.js</span></code></pre></td></tr></table></div></figure>


<h2>5. Now you can try in browser</h2>

<p>http://your.ip:3000/https://www.google.com</p>

<h2>6. Run your Preloader service after restart Machine</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>cd ~/prerender
</span><span class='line'>vim startup.sh</span></code></pre></td></tr></table></div></figure>


<p>Put these lines to the file</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>#!/usr/bin/env bash
</span><span class='line'>export LANGUAGE=en_US.UTF-8
</span><span class='line'>export LANG=en_US.UTF-8
</span><span class='line'>export LC_ALL=en_US.UTF-8
</span><span class='line'>node /home/deploy/prerender/server.js</span></code></pre></td></tr></table></div></figure>


<p>Allow the script can be excuted</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>chmod +x startup.sh</span></code></pre></td></tr></table></div></figure>


<p>Setup <code>/etc/init/prerender.conf</code></p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>sudo vim /etc/init/prerender.conf</span></code></pre></td></tr></table></div></figure>


<p>Put these lines to the file</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>#!upstart
</span><span class='line'>description "A job that runs the prerender service"
</span><span class='line'>author "Deploy"
</span><span class='line'>
</span><span class='line'>start on filesystem or runlevel [2345]
</span><span class='line'>script
</span><span class='line'>    export HOME="/home/deploy/prerender"
</span><span class='line'>    cd /home/deploy/prerender
</span><span class='line'>    exec su -c ' /home/deploy/prerender/startup.sh'
</span><span class='line'>end script
</span><span class='line'>
</span><span class='line'>pre-start script
</span><span class='line'>    echo "['date'] Prerender Service Starting" &gt;&gt; /var/log/prerender/prerender.log
</span><span class='line'>end script
</span><span class='line'>
</span><span class='line'>pre-stop script
</span><span class='line'>    echo "['date'] Prerender Service Stopping" &gt;&gt; /var/log/prerender/prerender.log
</span><span class='line'>end script
</span><span class='line'>
</span><span class='line'>respawn
</span><span class='line'>respawn limit 10 90</span></code></pre></td></tr></table></div></figure>


<p>Before start you need to create this folder</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>sudo mkdir /var/log/prerender</span></code></pre></td></tr></table></div></figure>


<p>Now you can</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>sudo service prerender start</span></code></pre></td></tr></table></div></figure>



]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[How to setup a simple standalone faye websocket server using SSL and Ruby]]></title>
    <link href="http://ccaloha.cc/blog/2016/06/16/how-to-setup-standalone-faye-websocket-server-using-ssl-and-ruby/"/>
    <updated>2016-06-16T12:08:11+08:00</updated>
    <id>http://ccaloha.cc/blog/2016/06/16/how-to-setup-standalone-faye-websocket-server-using-ssl-and-ruby</id>
    <content type="html"><![CDATA[<p><img src='https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/websocket/How%20to%20setup%20a%20simple%20standalone%20faye%20websocket%20server%20using%20SSL%20and%20Ruby.png' alt='Demo faye websocket server using Ruby'></p>

<p>References:</p>

<p><a href="https://www.driftingruby.com/episodes/faye-websockets-part-1">faye-websockets-part-1</a></p>

<p><a href="https://www.driftingruby.com/episodes/faye-websockets-part-2">faye-websockets-part-2</a></p>

<p><a href="http://code.tutsplus.com/tutorials/how-to-use-faye-as-a-real-time-push-server-in-rails--net-22600">How to Use Faye as a Real-Time Push Server in Rails</a></p>

<p><strong>====================================================</strong></p>

<p><strong>In your remote Ubuntu server</strong></p>

<p><strong>====================================================</strong></p>

<h2>Step1. Install Ruby environment in Ubuntu</h2>

<p>Reference: <a href="https://gorails.com/deploy/ubuntu/14.04">https://gorails.com/deploy/ubuntu/14.04</a></p>

<h3>Add user</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>sudo adduser deploy
</span><span class='line'>sudo adduser deploy sudo
</span><span class='line'>su deploy</span></code></pre></td></tr></table></div></figure>


<h3>Make sure you can login using new user</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>cd ~
</span><span class='line'>mkdir .ssh
</span><span class='line'>cd .ssh
</span><span class='line'>vim authorized_keys</span></code></pre></td></tr></table></div></figure>


<p>then put the content in <code>~/.ssh/id_rsa.pub</code> in your <em>local machine</em> into it.</p>

<h3>Install Ruby and RVM</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>sudo apt-get update
</span><span class='line'>
</span><span class='line'>sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties libffi-dev
</span><span class='line'>
</span><span class='line'>sudo apt-get install libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
</span><span class='line'>
</span><span class='line'>gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
</span><span class='line'>
</span><span class='line'>curl -L https://get.rvm.io | bash -s stable
</span><span class='line'>
</span><span class='line'>source ~/.rvm/scripts/rvm
</span><span class='line'>
</span><span class='line'>rvm install 2.2.3
</span><span class='line'>
</span><span class='line'>gem install bundler</span></code></pre></td></tr></table></div></figure>




<!--more-->


<h3>Create fake SSL for thin server</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mkdir -p /home/deploy/faye-server/shared/thin/ssl
</span><span class='line'>openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /home/deploy/faye-server/shared/thin/ssl/thin.key -out /home/deploy/faye-server/shared/thin/ssl/thin.crt</span></code></pre></td></tr></table></div></figure>


<h3>Create .env file</h3>

<p><code>vim /home/deploy/faye-server/shared/.env</code></p>

<p>put like this</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>AUTH_TOKEN: 'your_secret'</span></code></pre></td></tr></table></div></figure>


<h3>Create thin.yml file</h3>

<p>ps. copied from <code>config/thin.yml.example</code></p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>port: 8080
</span><span class='line'>user: deploy
</span><span class='line'>group: deploy
</span><span class='line'>pid: /home/deploy/faye-server/current/faye/pids/thin.pid
</span><span class='line'>timeout: 30
</span><span class='line'>wait: 30
</span><span class='line'>log: /home/deploy/faye-server/current/log/thin.log
</span><span class='line'>max_conns: 4096
</span><span class='line'>require: []
</span><span class='line'>environment: production
</span><span class='line'>max_persistent_conns: 1024
</span><span class='line'>servers: 1
</span><span class='line'>threaded: true
</span><span class='line'>no-epoll: true
</span><span class='line'>daemonize: true
</span><span class='line'>chdir: /home/deploy/faye-server/current
</span><span class='line'>tag: faye
</span><span class='line'>ssl: true
</span><span class='line'>ssl-key-file: /home/deploy/faye-server/current/thin/ssl/thin.key
</span><span class='line'>ssl-cert-file: /home/deploy/faye-server/current/thin/ssl/thin.crt
</span><span class='line'>ssl-disable-verify: true
</span></code></pre></td></tr></table></div></figure>


<p><strong>====================================================</strong></p>

<p><strong>In your local machine</strong></p>

<p><strong>====================================================</strong></p>

<h2>Step2. Clone this project</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>git clone https://github.com/alChaCC/faye-server.git</span></code></pre></td></tr></table></div></figure>


<h3>Update Some Configurations</h3>

<p><em>config/deploy/staging.rb</em></p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>server 'your server ip'</span></code></pre></td></tr></table></div></figure>


<h2>Step3. Deployment</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>cap staging deploy</span></code></pre></td></tr></table></div></figure>


<h2>How to test?</h2>

<p>Before start you have to update the <em>IP</em> in <code>faye-server/test/index.html</code> and <code>faye-server/test/test.rb</code></p>

<p>And also the <strong>secret</strong> in <code>faye-server/test/test.rb</code></p>

<p>And open browser then type: <code>https://your.server.ip:8080/faye/faye.js</code>.</p>

<p>Since we make fake ssl, we need to tell browser to accept it anyway.</p>

<p><img src='https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/websocket/let%20chrome%20allow%20to%20process.png' alt='tell chrome to process anyway'></p>

<p>you have to make sure that you can see this.</p>

<p><img src='https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/websocket/faye%20js%20you%20should%20see.png' alt='faye js you should see'></p>

<p>Then, you can type in console</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>cd faye-server/test
</span><span class='line'>python -m SimpleHTTPServer 8000</span></code></pre></td></tr></table></div></figure>


<p>and also</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>irb
</span><span class='line'>require './test'
</span><span class='line'>t = SimpleTest.new
</span><span class='line'>t.tester(1, 100)</span></code></pre></td></tr></table></div></figure>


<p>Now you can open browser and type <code>http://localhost:8000/</code></p>

<p><img src='https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/websocket/How%20to%20setup%20a%20simple%20standalone%20faye%20websocket%20server%20using%20SSL%20and%20Ruby.png' alt='Demo faye websocket server using Ruby'></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Ruby on Rails Development Using Mongoid 5.0.0 - 3. How to use MapReduce to get pageview data]]></title>
    <link href="http://ccaloha.cc/blog/2016/01/07/ruby-on-rails-development-using-mongoid-5-dot-0-0-3-how-to-use-mapreduce-to-get-pageview-data/"/>
    <updated>2016-01-07T16:38:55+08:00</updated>
    <id>http://ccaloha.cc/blog/2016/01/07/ruby-on-rails-development-using-mongoid-5-dot-0-0-3-how-to-use-mapreduce-to-get-pageview-data</id>
    <content type="html"><![CDATA[<p>What is MapReduce?</p>

<blockquote><p>Map-reduce is a data processing paradigm for condensing large volumes of data into useful aggregated results.</p></blockquote>

<p><strong>MapReduce</strong> is a popular big data term in recent years proposed by Google. It is a method for manipulate large data sets parallelly and distributedly on   many machines. In my words, I usually said that Map-Reduce, &#8220;Map&#8221; is to assign match function to many machines for cutting a huge data into small data sets(group matched data), and then use &#8220;Reduce&#8221; to aggregate these calculated data.</p>

<blockquote><p>In this map-reduce operation, MongoDB applies the map phase to each input document (i.e. the documents in the collection that match the query condition). The map function emits key-value pairs. For those keys that have multiple values, MongoDB applies the reduce phase, which collects and condenses the aggregated data. MongoDB then stores the results in a collection. Optionally, the output of the reduce function may pass through a finalize function to further condense or process the results of the aggregation.</p></blockquote>

<p><img src='https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Ruby%20on%20Rails%20Development%20Using%20Mongoid%205.0.0%20-%203.%20How%20to%20use%20MapReduce%20to%20get%20pageview%20data/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7%202016-01-06%2016.12.54.png' alt='MongoDB MapReduce'></p>

<p>if you want more details, please check <a href="https://docs.mongodb.org/manual/core/map-reduce/">official documents</a></p>

<p>Let&#8217;s go check how to use <strong>map reduce</strong> in Mongoid.</p>

<!--more-->


<p>I also write an article for aggregation method. Please check: <a href="http://ccaloha.cc/blog/2016/01/06/ruby-on-rails-development-using-mongoid-5-dot-0-0-2-how-to-use-aggregation-to-get-pageview-data/">Ruby on Rails Development Using Mongoid 5.0.0 - 2. How to Use Aggregation to Get Pageview Data</a></p>

<p>Again, Here are my models,</p>

<p><img src='https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Ruby%20on%20Rails%20Development%20Using%20Mongoid%20-%202%20How%20to%20use%20Aggregation%20to%20get%20pageview%20data/model.png' alt='Mongoid map-reduce model example'></p>

<h2>Calculate Unique Pageviews from 2016/1/1 to 2016/1/5</h2>

<p>First, MongoDB use custom Javascript functions to do map and reduce operations. So, we need to write JS code as a string and pass to MongoDB.</p>

<p>In this example, I hope that the same page a user watched in the same day should be calculated just one time(eg. If I watch path &#8216;/&#8217; 3 times a day, the pageview should be 1 not 3). So, I call it unique pageviews.</p>

<h3>Let&#8217;s Check Map Function</h3>

<p>I use &#8220;pathname&#8221; as an identifier(key) and use {&#8220;total&#8221;: 1} as a value. In my opinion, value is the result that you want to obtain.</p>

<blockquote><p>In the map function, reference the current document as <strong>this</strong> within the function.</p></blockquote>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>map = %q{ 
</span><span class='line'>              function() {
</span><span class='line'>                emit(
</span><span class='line'>                  this.pathname,
</span><span class='line'>                  {
</span><span class='line'>                    total: 1
</span><span class='line'>                  } 
</span><span class='line'>                );
</span><span class='line'>              } 
</span><span class='line'>          }
</span></code></pre></td></tr></table></div></figure>


<h3>How About Reduce Function?</h3>

<p>Reduce function will get the key and value from map function.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>reduce = %q{
</span><span class='line'>              function(key, values){
</span><span class='line'>                print("=====================");
</span><span class='line'>                print("Key is:" + key);
</span><span class='line'>                print("Value is:" + JSON.stringify(values));
</span><span class='line'>                var r = { total: 0};
</span><span class='line'>                for (var idx  = 0; idx &lt; values.length; idx++) {
</span><span class='line'>                  var tt = values[idx].total;
</span><span class='line'>                  r.total += tt
</span><span class='line'>                }
</span><span class='line'>                return r;
</span><span class='line'>              }
</span><span class='line'>            }</span></code></pre></td></tr></table></div></figure>


<blockquote><p>Tips: we can use &#8220;print&#8221; for debugging.</p></blockquote>

<h3>Using MapReduce in Mongoid</h3>

<p>Now, we can put map and reduce function together.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>res = PageTracking.where(:created_at.gte =&gt; Time.parse('2016-01-01').beginning_of_day , 
</span><span class='line'>:created_at.lte =&gt; Time.parse('2016-01-05').end_of_day)
</span><span class='line'>.order_by(:created_at =&gt; 'desc')
</span><span class='line'>.map_reduce(map,reduce)
</span><span class='line'>.out(inline: true)</span></code></pre></td></tr></table></div></figure>


<p>console will show like below</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>=&gt; #&lt;Mongoid::Contextual::MapReduce
</span><span class='line'>  selector: {"created_at"=&gt;{"$gte"=&gt;2015-12-31 16:00:00 UTC, "$lte"=&gt;2016-01-05 15:59:59 UTC}}
</span><span class='line'>  class:    PageTracking
</span><span class='line'>  map:
</span><span class='line'>              function() {
</span><span class='line'>                emit(
</span><span class='line'>                  this.pathname,
</span><span class='line'>                  {
</span><span class='line'>                    total: 1
</span><span class='line'>                  }
</span><span class='line'>);
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>  reduce:
</span><span class='line'>              function(key, values){
</span><span class='line'>                print("=====================");
</span><span class='line'>                print("Key is:" + key);
</span><span class='line'>                print("Value is:" + JSON.stringify(values));
</span><span class='line'>                var r = { total: 0};
</span><span class='line'>                for (var idx  = 0; idx &lt; values.length; idx++) {
</span><span class='line'>                  var tt = values[idx].total;
</span><span class='line'>                  r.total += tt
</span><span class='line'>                }
</span><span class='line'>return r;
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>  finalize:
</span><span class='line'>  out:      {:inline=&gt;true}&gt;
</span></code></pre></td></tr></table></div></figure>


<p>Let&#8217;s get all results.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>res.to_a</span></code></pre></td></tr></table></div></figure>


<p>Here is my result.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>[{"_id"=&gt;"/", "value"=&gt;{"total"=&gt;3.0}},
</span><span class='line'> {"_id"=&gt;"/activities", "value"=&gt;{"total"=&gt;1.0}},
</span><span class='line'> {"_id"=&gt;"/activities/hadalabo01", "value"=&gt;{"total"=&gt;1.0}},
</span><span class='line'> {"_id"=&gt;"/beautybuzz", "value"=&gt;{"total"=&gt;1.0}},
</span><span class='line'> {"_id"=&gt;"/beautynews", "value"=&gt;{"total"=&gt;1.0}}]</span></code></pre></td></tr></table></div></figure>


<p>ps. If you want to check the debugging message, you can</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># check the location of MongoDB system log
</span><span class='line'>cat /usr/local/etc/mongod.conf
</span><span class='line'>
</span><span class='line'># In my case, it will be located in /usr/local/var/log/mongodb/mongo.log
</span><span class='line'>tail -1000f /usr/local/var/log/mongodb/mongo.log</span></code></pre></td></tr></table></div></figure>


<p></p>

<p>And I see</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>2016-01-07T11:49:24.460+0800 I -        [conn3] =====================
</span><span class='line'>2016-01-07T11:49:24.460+0800 I -        [conn3] Key is:/
</span><span class='line'>2016-01-07T11:49:24.460+0800 I -        [conn3] Value is:[{"total":1},{"total":1},{"total":1}]</span></code></pre></td></tr></table></div></figure>


<p>It&#8217;s weird, in result, I have many different &#8220;_id&#8221; such as &#8216;/&#8217;, &#8216;/activities&#8217;, &#8216;/beautybuzz&#8217;. But, in MongoDB system log, I just see &#8216;/&#8217;. Why?</p>

<p>According to <a href="https://docs.mongodb.org/manual/reference/command/mapReduce/#requirements-for-the-reduce-function">Requirements for the reduce Function</a>.</p>

<blockquote><p>MongoDB will not call the reduce function for a key that has only a single value. The values argument is an array whose elements are the value objects that are “mapped” to the key.</p></blockquote>

<p>Let&#8217;s check the data in my MongoDB,</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>PageTracking.where(:created_at.gte =&gt; Time.parse('2016-01-01').beginning_of_day , :created_at.lte =&gt; Time.parse('2016-01-05').end_of_day).order_by(:created_at =&gt; 'desc').to_a</span></code></pre></td></tr></table></div></figure>


<p>As you can see, pathname except &#8216;/&#8217; are just have one record. So, it will not be handled by reduce function.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>[#&lt;PageTracking _id: 568b78c07db99b9a30000001, created_at: 2016-01-04 16:00:00 UTC, updated_at: 2016-01-05 08:03:12 UTC, pathname: "/beautynews", daily_total_count: 1, user_id: BSON::ObjectId('568b28e67db99b1ae8000003')&gt;,
</span><span class='line'> #&lt;PageTracking _id: 568b7e5e7db99b9a30000017, created_at: 2016-01-04 16:00:00 UTC, updated_at: 2016-01-05 08:27:10 UTC, pathname: "/beautybuzz", daily_total_count: 1, user_id: BSON::ObjectId('568b28e67db99b1ae8000003')&gt;,
</span><span class='line'> #&lt;PageTracking _id: 568b7d8e7db99b9a30000013, created_at: 2016-01-04 16:00:00 UTC, updated_at: 2016-01-05 08:23:42 UTC, pathname: "/activities/hadalabo01", daily_total_count: 1, user_id: BSON::ObjectId('568b28e67db99b1ae8000003')&gt;,
</span><span class='line'> #&lt;PageTracking _id: 568b7d307db99b9a30000011, created_at: 2016-01-04 16:00:00 UTC, updated_at: 2016-01-05 08:22:08 UTC, pathname: "/activities", daily_total_count: 1, user_id: BSON::ObjectId('568b28e67db99b1ae8000003')&gt;,
</span><span class='line'> #&lt;PageTracking _id: 568b28e67db99b1ae8000005, created_at: 2016-01-04 16:00:00 UTC, updated_at: 2016-01-05 02:22:30 UTC, pathname: "/", daily_total_count: 3, user_id: BSON::ObjectId('568b28e67db99b1ae8000003')&gt;,
</span><span class='line'> #&lt;PageTracking _id: 568b28ad7db99b1ae8000002, created_at: 2016-01-04 16:00:00 UTC, updated_at: 2016-01-05 02:21:33 UTC, pathname: "/", daily_total_count: 1, user_id: BSON::ObjectId('568b28ad7db99b1ae8000000')&gt;,
</span><span class='line'> #&lt;PageTracking _id: 568a1ef77db99bc968000002, created_at: 2016-01-03 16:00:00 UTC, updated_at: 2016-01-04 07:27:51 UTC, pathname: "/", daily_total_count: 2, user_id: BSON::ObjectId('568a1ef77db99bc968000000')&gt;]</span></code></pre></td></tr></table></div></figure>


<p>If I watch &#8216;/activities&#8217; again, and run MapReduce again.</p>

<p>yay, I can see /activities in system log.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>2016-01-07T14:42:23.349+0800 I -        [conn3] =====================
</span><span class='line'>2016-01-07T14:42:23.349+0800 I -        [conn3] Key is:/
</span><span class='line'>2016-01-07T14:42:23.350+0800 I -        [conn3] Value is:[{"total":1},{"total":1},{"total":1}]
</span><span class='line'>2016-01-07T14:42:23.350+0800 I -        [conn3] =====================
</span><span class='line'>2016-01-07T14:42:23.350+0800 I -        [conn3] Key is:/activities
</span><span class='line'>2016-01-07T14:42:23.350+0800 I -        [conn3] Value is:[{"total":1},{"total":1}]</span></code></pre></td></tr></table></div></figure>


<h2>Calculate Non-Unique Pageviews</h2>

<p>In this example, I don&#8217;t care a user watched same page in the same day. I want all pageviews. (eg. If I watch path &#8216;/&#8217; 3 times a day, the pageview should be 3). So, I call it non-unique pageviews.</p>

<p>The only thing I have to update is map function. Change <strong>total: 1</strong> to <strong>total: this.daily_total_count</strong></p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>total_map = %q{ 
</span><span class='line'>              function() {
</span><span class='line'>                emit(
</span><span class='line'>                  this.pathname,
</span><span class='line'>                  {
</span><span class='line'>                    total: this.daily_total_count
</span><span class='line'>                  } 
</span><span class='line'>                );
</span><span class='line'>              } 
</span><span class='line'>          }
</span></code></pre></td></tr></table></div></figure>


<p>yay!</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>[{"_id"=&gt;"/", "value"=&gt;{"total"=&gt;6.0}},
</span><span class='line'> {"_id"=&gt;"/activities", "value"=&gt;{"total"=&gt;2.0}},
</span><span class='line'> {"_id"=&gt;"/activities/hadalabo01", "value"=&gt;{"total"=&gt;1.0}},
</span><span class='line'> {"_id"=&gt;"/beautybuzz", "value"=&gt;{"total"=&gt;1.0}},
</span><span class='line'> {"_id"=&gt;"/beautynews", "value"=&gt;{"total"=&gt;1.0}}]</span></code></pre></td></tr></table></div></figure>


<h2>Calculate Pageviews and Get Who Watch The Page</h2>

<p>In this example, beside calculate pageviews, I want to get user who watch the page as well.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>map_with_user = %q{ 
</span><span class='line'>              function() {
</span><span class='line'>                var key = {
</span><span class='line'>                  pathname: this.pathname
</span><span class='line'>                };
</span><span class='line'>                emit(
</span><span class='line'>                  key,
</span><span class='line'>                  {
</span><span class='line'>                    total:  this.daily_total_count,
</span><span class='line'>                    user_id:   this.user_id
</span><span class='line'>                  } 
</span><span class='line'>                );
</span><span class='line'>              } 
</span><span class='line'>          }
</span><span class='line'>
</span><span class='line'>reduce_with_user = %q{
</span><span class='line'>              function(key, values){
</span><span class='line'>                print("=====================");
</span><span class='line'>                print("Key is:" + JSON.stringify(key));
</span><span class='line'>                print("Value is:" +  JSON.stringify(values));
</span><span class='line'>                var r = { 
</span><span class='line'>                    total: 0,
</span><span class='line'>                    users: []
</span><span class='line'>                };
</span><span class='line'>                for (var idx  = 0; idx &lt; values.length; idx++) {
</span><span class='line'>                    r.total += values[idx].total;
</span><span class='line'>                    r.users.push(values[idx].user_id);
</span><span class='line'>                }
</span><span class='line'>                return r;
</span><span class='line'>              }
</span><span class='line'>            }
</span><span class='line'>
</span><span class='line'>PageTracking.where(:created_at.gte =&gt; Time.parse('2016-01-01').beginning_of_day , 
</span><span class='line'>:created_at.lte =&gt; Time.parse('2016-01-07').end_of_day)
</span><span class='line'>.order_by(:created_at =&gt; 'desc')
</span><span class='line'>.map_reduce(map_with_user,reduce_with_user)
</span><span class='line'>.out(inline: true).to_a
</span></code></pre></td></tr></table></div></figure>


<blockquote><p>Tips: key can also be a hash.</p></blockquote>

<p>Since we want to get all users so I add <strong>users: []</strong>, then I just push user_id in it.</p>

<p>The result is&#8230;</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>[
</span><span class='line'>{  
</span><span class='line'>  "_id"  =&gt;  {  
</span><span class='line'>    "pathname"    =&gt;"/"
</span><span class='line'>  },
</span><span class='line'>  "value"  =&gt;  {  
</span><span class='line'>    "total"    =&gt;6.0,
</span><span class='line'>    "users"    =&gt;    [  
</span><span class='line'>      BSON::ObjectId('568b28e67db99b1ae8000003'),
</span><span class='line'>      BSON::ObjectId('568b28ad7db99b1ae8000000'),
</span><span class='line'>      BSON::ObjectId('568a1ef77db99bc968000000')
</span><span class='line'>    ]
</span><span class='line'>  }
</span><span class='line'>},
</span><span class='line'>{  
</span><span class='line'>  "_id"  =&gt;  {  
</span><span class='line'>    "pathname"    =&gt;"/activities"
</span><span class='line'>  },
</span><span class='line'>  "value"  =&gt;  {  
</span><span class='line'>    "total"    =&gt;3.0,
</span><span class='line'>    "users"    =&gt;    [  
</span><span class='line'>      BSON::ObjectId('568e08937db99b12c1000000'),
</span><span class='line'>      BSON::ObjectId('568b28e67db99b1ae8000003')
</span><span class='line'>    ]
</span><span class='line'>  }
</span><span class='line'>},
</span><span class='line'>{  
</span><span class='line'>  "_id"  =&gt;  {  
</span><span class='line'>    "pathname"    =&gt;"/activities/hadalabo01"
</span><span class='line'>  },
</span><span class='line'>  "value"  =&gt;  {  
</span><span class='line'>    "total"    =&gt;1.0,
</span><span class='line'>    "user_id"    =&gt;BSON::ObjectId('568b28e67db99b1ae8000003')
</span><span class='line'>  }
</span><span class='line'>},
</span><span class='line'>{  
</span><span class='line'>  "_id"  =&gt;  {  
</span><span class='line'>    "pathname"    =&gt;"/beautybuzz"
</span><span class='line'>  },
</span><span class='line'>  "value"  =&gt;  {  
</span><span class='line'>    "total"    =&gt;1.0,
</span><span class='line'>    "user_id"    =&gt;BSON::ObjectId('568b28e67db99b1ae8000003')
</span><span class='line'>  }
</span><span class='line'>},
</span><span class='line'>{  
</span><span class='line'>  "_id"  =&gt;  {  
</span><span class='line'>    "pathname"    =&gt;"/beautynews"
</span><span class='line'>  },
</span><span class='line'>  "value"  =&gt;  {  
</span><span class='line'>    "total"    =&gt;1.0,
</span><span class='line'>    "user_id"    =&gt;BSON::ObjectId('568b28e67db99b1ae8000003')
</span><span class='line'>  }
</span><span class='line'>}
</span><span class='line'>]</span></code></pre></td></tr></table></div></figure>


<p>Take a look of system log, to see what&#8217;s key and values pass to reduce function.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>2016-01-07T15:43:24.520+0800 I -        [conn3] =====================
</span><span class='line'>2016-01-07T15:43:24.520+0800 I -        [conn3] Key is:{"pathname":"/"}
</span><span class='line'>2016-01-07T15:43:24.521+0800 I -        [conn3] Value is:[{"total":3,"user_id":{"str":"568b28e67db99b1ae8000003"}},{"total":1,"user_id":{"str":"568b28ad7db99b1ae8000000"}},{"total":2,"user_id":{"str":"568a1ef77db99bc968000000"}}]
</span><span class='line'>2016-01-07T15:43:24.521+0800 I -        [conn3] =====================
</span><span class='line'>2016-01-07T15:43:24.521+0800 I -        [conn3] Key is:{"pathname":"/activities"}
</span><span class='line'>2016-01-07T15:43:24.521+0800 I -        [conn3] Value is:[{"total":2,"user_id":{"str":"568e08937db99b12c1000000"}},{"total":1,"user_id":{"str":"568b28e67db99b1ae8000003"}}]</span></code></pre></td></tr></table></div></figure>


<p>Here, you will notice that there are some values has <strong>user_id</strong> and some has <strong>users</strong>. Again, since some keys only has single value, it will not be processed by reduce function.</p>

<h2>Use &#8220;finalize&#8221; function to beautify the result</h2>

<p>It&#8217;s annoying, if data format is not consistent. So, I use finalize function to solve this problem.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>finalize = %q{
</span><span class='line'>  function(key, reducedValue){
</span><span class='line'>    print("=====================");
</span><span class='line'>    print("Key is:" + JSON.stringify(key));
</span><span class='line'>    print("reducedValue is:" + JSON.stringify(reducedValue));
</span><span class='line'>    var result = {
</span><span class='line'>      user_data: []
</span><span class='line'>    }
</span><span class='line'>    if(reducedValue.users){
</span><span class='line'>      reducedValue.users.forEach(function(u){
</span><span class='line'>        result.user_data.push(u);
</span><span class='line'>       });
</span><span class='line'>    } else {
</span><span class='line'>      result.user_data.push(reducedValue.user_id);
</span><span class='line'>    }
</span><span class='line'>    return result
</span><span class='line'>  }
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>PageTracking.where(:created_at.gte =&gt; Time.parse('2016-01-01').beginning_of_day , 
</span><span class='line'>:created_at.lte =&gt; Time.parse('2016-01-07').end_of_day)
</span><span class='line'>.order_by(:created_at =&gt; 'desc')
</span><span class='line'>.map_reduce(map_with_user,reduce_with_user)
</span><span class='line'>.finalize(finalize)
</span><span class='line'>.out(inline: true).to_a</span></code></pre></td></tr></table></div></figure>


<p>Yay, every results looks same~~</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>[
</span><span class='line'>{  
</span><span class='line'>  "_id"  =&gt;  {  
</span><span class='line'>    "pathname"    =&gt;"/"
</span><span class='line'>  },
</span><span class='line'>  "value"  =&gt;  {  
</span><span class='line'>    "user_data"    =&gt;    [  
</span><span class='line'>      BSON::ObjectId('568b28e67db99b1ae8000003'),
</span><span class='line'>      BSON::ObjectId('568b28ad7db99b1ae8000000'),
</span><span class='line'>      BSON::ObjectId('568a1ef77db99bc968000000')
</span><span class='line'>    ]
</span><span class='line'>  }
</span><span class='line'>},
</span><span class='line'>{  
</span><span class='line'>  "_id"  =&gt;  {  
</span><span class='line'>    "pathname"    =&gt;"/activities"
</span><span class='line'>  },
</span><span class='line'>  "value"  =&gt;  {  
</span><span class='line'>    "user_data"    =&gt;    [  
</span><span class='line'>      BSON::ObjectId('568e08937db99b12c1000000'),
</span><span class='line'>      BSON::ObjectId('568b28e67db99b1ae8000003')
</span><span class='line'>    ]
</span><span class='line'>  }
</span><span class='line'>},
</span><span class='line'>{  
</span><span class='line'>  "_id"  =&gt;  {  
</span><span class='line'>    "pathname"    =&gt;"/activities/hadalabo01"
</span><span class='line'>  },
</span><span class='line'>  "value"  =&gt;  {  
</span><span class='line'>    "user_data"    =&gt;    [  
</span><span class='line'>      BSON::ObjectId('568b28e67db99b1ae8000003')
</span><span class='line'>    ]
</span><span class='line'>  }
</span><span class='line'>},
</span><span class='line'>{  
</span><span class='line'>  "_id"  =&gt;  {  
</span><span class='line'>    "pathname"    =&gt;"/beautybuzz"
</span><span class='line'>  },
</span><span class='line'>  "value"  =&gt;  {  
</span><span class='line'>    "user_data"    =&gt;    [  
</span><span class='line'>      BSON::ObjectId('568b28e67db99b1ae8000003')
</span><span class='line'>    ]
</span><span class='line'>  }
</span><span class='line'>},
</span><span class='line'>{  
</span><span class='line'>  "_id"  =&gt;  {  
</span><span class='line'>    "pathname"    =&gt;"/beautynews"
</span><span class='line'>  },
</span><span class='line'>  "value"  =&gt;  {  
</span><span class='line'>    "user_data"    =&gt;    [  
</span><span class='line'>      BSON::ObjectId('568b28e67db99b1ae8000003')
</span><span class='line'>    ]
</span><span class='line'>  }
</span><span class='line'>}]</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Ruby on Rails Development Using Mongoid 5.0.0 - 2. How to use Aggregation to get pageview data]]></title>
    <link href="http://ccaloha.cc/blog/2016/01/06/ruby-on-rails-development-using-mongoid-5-dot-0-0-2-how-to-use-aggregation-to-get-pageview-data/"/>
    <updated>2016-01-06T13:57:37+08:00</updated>
    <id>http://ccaloha.cc/blog/2016/01/06/ruby-on-rails-development-using-mongoid-5-dot-0-0-2-how-to-use-aggregation-to-get-pageview-data</id>
    <content type="html"><![CDATA[<p>What is Aggregation ?</p>

<blockquote><p>Aggregations are operations that process data records and return computed results.</p></blockquote>

<p>In my opinion, it just like queries but it can do some operations step by step when processing the query just like a pipeline.</p>

<p><img src='https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Ruby%20on%20Rails%20Development%20Using%20Mongoid%20-%202%20How%20to%20use%20Aggregation%20to%20get%20pageview%20data/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7%202016-01-05%2017.31.06.png' alt='MongoDB Aggregation'></p>

<p>if you want more details, please check <a href="https://docs.mongodb.org/manual/core/aggregation-introduction/">official documents</a></p>

<!--more-->


<p>Here are my models,</p>

<p><img src='https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Ruby%20on%20Rails%20Development%20Using%20Mongoid%20-%202%20How%20to%20use%20Aggregation%20to%20get%20pageview%20data/model.png' alt='aggregation model example'></p>

<h2>Get Data using &#8220;match&#8221;</h2>

<p>If I want to find pageview from 2016/1/1 to 2016/1/5</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>sort_stage = {
</span><span class='line'>        '$sort' =&gt; { 'created_at' =&gt; 1 }
</span><span class='line'>    }
</span><span class='line'>    
</span><span class='line'>match_stage = {
</span><span class='line'>          '$match' =&gt; {
</span><span class='line'>            "created_at" =&gt; {
</span><span class='line'>              "$gte" =&gt; Time.parse('2016-01-01').beginning_of_day, 
</span><span class='line'>              "$lte" =&gt; Time.parse('2016-01-05').end_of_day
</span><span class='line'>            }
</span><span class='line'>          }
</span><span class='line'>        }
</span><span class='line'>
</span><span class='line'>result = PageTracking.collection.aggregate([sort_stage,match_stage])
</span><span class='line'>result.each do |page|
</span><span class='line'>  puts page
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<p>Here is my result:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>{"_id"=&gt;BSON::ObjectId('568a1ef77db99bc968000002'), "pathname"=&gt;"/", "daily_total_count"=&gt;2, "created_at"=&gt;2016-01-03 16:00:00 UTC, "user_id"=&gt;BSON::ObjectId('568a1ef77db99bc968000000'), "updated_at"=&gt;2016-01-04 07:27:51 UTC }
</span><span class='line'>{"_id"=&gt;BSON::ObjectId('568b28ad7db99b1ae8000002'), "pathname"=&gt;"/", "daily_total_count"=&gt;1, "created_at"=&gt;2016-01-04 16:00:00 UTC, "user_id"=&gt;BSON::ObjectId('568b28ad7db99b1ae8000000'), "updated_at"=&gt;2016-01-05 02:21:33 UTC}
</span><span class='line'>{"_id"=&gt;BSON::ObjectId('568b28e67db99b1ae8000005'), "pathname"=&gt;"/", "daily_total_count"=&gt;3, "created_at"=&gt;2016-01-04 16:00:00 UTC, "user_id"=&gt;BSON::ObjectId('568b28e67db99b1ae8000003'), "updated_at"=&gt;2016-01-05 02:22:30 UTC}
</span><span class='line'>{"_id"=&gt;BSON::ObjectId('568b7d307db99b9a30000011'), "pathname"=&gt;"/activities", "daily_total_count"=&gt;1, "created_at"=&gt;2016-01-04 16:00:00 UTC, "user_id"=&gt;BSON::ObjectId('568b28e67db99b1ae8000003'), "updated_at"=&gt;2016-01-05 08:22:08 UTC}
</span><span class='line'>{"_id"=&gt;BSON::ObjectId('568b7d8e7db99b9a30000013'), "pathname"=&gt;"/activities/hadalabo01", "daily_total_count"=&gt;1, "created_at"=&gt;2016-01-04 16:00:00 UTC, "user_id"=&gt;BSON::ObjectId('568b28e67db99b1ae8000003'), "updated_at"=&gt;2016-01-05 08:23:42 UTC}
</span><span class='line'>{"_id"=&gt;BSON::ObjectId('568b7e5e7db99b9a30000017'), "pathname"=&gt;"/beautybuzz", "daily_total_count"=&gt;1, "created_at"=&gt;2016-01-04 16:00:00 UTC, "user_id"=&gt;BSON::ObjectId('568b28e67db99b1ae8000003'), "updated_at"=&gt;2016-01-05 08:27:10 UTC}
</span><span class='line'>{"_id"=&gt;BSON::ObjectId('568b78c07db99b9a30000001'), "pathname"=&gt;"/beautynews", "daily_total_count"=&gt;1, "created_at"=&gt;2016-01-04 16:00:00 UTC, "user_id"=&gt;BSON::ObjectId('568b28e67db99b1ae8000003'), "updated_at"=&gt;2016-01-05 08:03:12 UTC}</span></code></pre></td></tr></table></div></figure>


<p>ps. I add a sort stage in front of match stage due to the official suggestion.<a href="https://docs.mongodb.org/manual/core/aggregation-pipeline-optimization/#sort-match-sequence-optimization">Aggregation Pipeline Optimization</a></p>

<h2>Group Data using &#8220;group&#8221;</h2>

<p>Oops, in fact, I want all records with <strong>pathname => &#8216;/&#8217;</strong> should be summed up to one record just like <strong>group_by</strong> in MySQL</p>

<p>So, we can add a new stage</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>group_stage = {
</span><span class='line'>          "$group" =&gt; {
</span><span class='line'>            "_id" =&gt; {
</span><span class='line'>              "pathname" =&gt; "$pathname"
</span><span class='line'>            },
</span><span class='line'>            "page_count" =&gt; { "$sum" =&gt; "$daily_total_count" }
</span><span class='line'>          }
</span><span class='line'>         }
</span><span class='line'>result = PageTracking.collection.aggregate([sort_stage,match_stage,group_stage])
</span><span class='line'>
</span><span class='line'>result.each do |page|
</span><span class='line'>  puts page
</span><span class='line'>end
</span></code></pre></td></tr></table></div></figure>


<p>which mean we can group by &#8220;pathname&#8221; and when we do grouping we can sum all daily_total_count into &#8220;page_count&#8221;</p>

<p>Here is my result</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>{"_id"=&gt;{"pathname"=&gt;"/beautybuzz"}, "page_count"=&gt;1}
</span><span class='line'>{"_id"=&gt;{"pathname"=&gt;"/activities/hadalabo01"}, "page_count"=&gt;1}
</span><span class='line'>{"_id"=&gt;{"pathname"=&gt;"/activities"}, "page_count"=&gt;1}
</span><span class='line'>{"_id"=&gt;{"pathname"=&gt;"/beautynews"}, "page_count"=&gt;1}
</span><span class='line'>{"_id"=&gt;{"pathname"=&gt;"/"}, "page_count"=&gt;6}</span></code></pre></td></tr></table></div></figure>


<h2>Add more information while grouping Data using &#8220;push&#8221;</h2>

<p>Ok, great! But, I got an another requirement, Hi, Aloha, Can you tell me that who see each pages?</p>

<p>Easy, let&#8217;s add some codes in group_stage</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>group_stage = {
</span><span class='line'>          "$group" =&gt; {
</span><span class='line'>            "_id" =&gt; {
</span><span class='line'>              "pathname" =&gt; "$pathname"
</span><span class='line'>            },
</span><span class='line'>            "page_count" =&gt; { "$sum" =&gt; "$daily_total_count" },
</span><span class='line'>            "users" =&gt; {
</span><span class='line'>              "$push" =&gt; {
</span><span class='line'>                "user" =&gt; "$user_id"
</span><span class='line'>              }
</span><span class='line'>            }
</span><span class='line'>          }
</span><span class='line'>         }
</span><span class='line'>result = PageTracking.collection.aggregate([sort_stage,match_stage,group_stage])
</span><span class='line'>
</span><span class='line'>result.each do |page|
</span><span class='line'>  puts page
</span><span class='line'>end
</span></code></pre></td></tr></table></div></figure>


<p>Here is my result</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>{"_id"=&gt;{"pathname"=&gt;"/beautybuzz"}, "page_count"=&gt;1, "users"=&gt;[{"user"=&gt;BSON::ObjectId('568b28e67db99b1ae8000003')}]}
</span><span class='line'>{"_id"=&gt;{"pathname"=&gt;"/activities/hadalabo01"}, "page_count"=&gt;1, "users"=&gt;[{"user"=&gt;BSON::ObjectId('568b28e67db99b1ae8000003')}]}
</span><span class='line'>{"_id"=&gt;{"pathname"=&gt;"/activities"}, "page_count"=&gt;1, "users"=&gt;[{"user"=&gt;BSON::ObjectId('568b28e67db99b1ae8000003')}]}
</span><span class='line'>{"_id"=&gt;{"pathname"=&gt;"/beautynews"}, "page_count"=&gt;1, "users"=&gt;[{"user"=&gt;BSON::ObjectId('568b28e67db99b1ae8000003')}]}
</span><span class='line'>{"_id"=&gt;{"pathname"=&gt;"/"}, "page_count"=&gt;6, "users"=&gt;[{"user"=&gt;BSON::ObjectId('568a1ef77db99bc968000000')}, {"user"=&gt;BSON::ObjectId('568b28ad7db99b1ae8000000')}, {"user"=&gt;BSON::ObjectId('568b28e67db99b1ae8000003')}]}</span></code></pre></td></tr></table></div></figure>


<p>Cool, our aggregation run smoothly.</p>

<h2>Aggregate Big Data</h2>

<p>After 3 months, we found that some fetch pageview operations will fail.</p>

<p>Oops, since these fetch operations use big time range (ex: get pageview data from 2015/9/1 ~ 2016/1/1)</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>match_stage = {
</span><span class='line'>          '$match' =&gt; {
</span><span class='line'>            "created_at" =&gt; {
</span><span class='line'>              "$gte" =&gt; Time.parse('2015-09-01').beginning_of_day, 
</span><span class='line'>              "$lte" =&gt; Time.parse('2016-01-01').end_of_day
</span><span class='line'>            }
</span><span class='line'>          }
</span><span class='line'>        }
</span><span class='line'>
</span><span class='line'>result = PageTracking.collection.aggregate([sort_stage,match_stage,group_stage])
</span><span class='line'>
</span><span class='line'>result.each do |page|
</span><span class='line'>  puts page
</span><span class='line'>end
</span></code></pre></td></tr></table></div></figure>


<p>I got this error message:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>aggregate | FAILED | exception: Exceeded memory limit for $group, but didn't allow external sort. Pass allowDiskUse:true to opt in. (16945) | 9.955263s
</span><span class='line'>Mongo::Error::OperationFailure: exception: Exceeded memory limit for $group, but didn't allow external sort. Pass allowDiskUse:true to opt in. (16945)</span></code></pre></td></tr></table></div></figure>


<p>How can we solve this problem?</p>

<p>Just like message says, add <strong>allowDiskUse:true</strong> to it.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>result = PageTracking.collection.aggregate(
</span><span class='line'>            [sort_stage,match_stage,group_stage],
</span><span class='line'>            :allow_disk_use =&gt; true
</span><span class='line'>          )
</span><span class='line'>result.each do |page|
</span><span class='line'>  puts page
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<p>Yay, bug fixed.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>...
</span><span class='line'>you will got a lots of data ....
</span><span class='line'>...</span></code></pre></td></tr></table></div></figure>


<h2>Limit data using &#8220;limit&#8221;</h2>

<p>if you just want 1000 data pass to next stage</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>sort_stage = {
</span><span class='line'>        '$sort' =&gt; { 'created_at' =&gt; -1 }
</span><span class='line'>    }
</span><span class='line'>
</span><span class='line'>limit_stage = {
</span><span class='line'>      '$limit' =&gt; 1000
</span><span class='line'>    }
</span><span class='line'>    
</span><span class='line'>result = UserPageTracking.collection.aggregate(
</span><span class='line'>      [sort_stage, limit_stage , match_stage, group_stage],
</span><span class='line'>      :allow_disk_use =&gt; true
</span><span class='line'>    )
</span><span class='line'>result.each do |page|
</span><span class='line'>  puts page
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<h2>Only want some specific data using &#8220;project&#8221;</h2>

<p>If fact, we don&#8217;t need created_at, updated_at attributes in group stage, so we can &#8230;</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>project_stage = 
</span><span class='line'>      { 
</span><span class='line'>        "$project" =&gt; { user_id: 1, pathname: 1, daily_total_count: 1}
</span><span class='line'>      }
</span><span class='line'>
</span><span class='line'>result = PageTracking.collection.aggregate(
</span><span class='line'>            [sort_stage,match_stage, project_stage, group_stage]
</span><span class='line'>          )
</span><span class='line'>result.each do |page|
</span><span class='line'>  puts page
</span><span class='line'>end
</span></code></pre></td></tr></table></div></figure>



]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[[HowTo] Integrate Google Analytics And Google AdWords for customize Ads retargeting]]></title>
    <link href="http://ccaloha.cc/blog/2015/12/04/howto-integrate-google-analytics-and-google-adwords-for-customize-ads-retargeting/"/>
    <updated>2015-12-04T14:08:20+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/12/04/howto-integrate-google-analytics-and-google-adwords-for-customize-ads-retargeting</id>
    <content type="html"><![CDATA[<iframe id="iframe_container" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen="" width="550" height="400" src="https://prezi.com/embed/n9iuo7dh40jc/?bgcolor=ffffff&amp;lock_to_path=0&amp;autoplay=0&amp;autohide_ctrls=0&amp;landing_data=bHVZZmNaNDBIWnNjdEVENDRhZDFNZGNIUE43MHdLNWpsdFJLb2ZHanI5bDNCUnpwRENZWkRYN3EvQ1N5VHhxdENnPT0&amp;landing_sign=e7opC2uX0_T7quFGuT0JpREwZJQtJ9MWUVT6tb-n_Lc"></iframe>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Ruby on Rails Development using Mongoid 5.0.0 - 1. Setup MongoDB]]></title>
    <link href="http://ccaloha.cc/blog/2015/10/03/ruby-on-rails-using-mongoid-5-dot-0-0-setup-mongodb/"/>
    <updated>2015-10-03T22:33:34+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/10/03/ruby-on-rails-using-mongoid-5-dot-0-0-setup-mongodb</id>
    <content type="html"><![CDATA[<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Ruby%20on%20Rails%20Development%20using%20Mongoid%205.0.0%20-%20Setup%20MongoDB/MongoDB-Logo.png" alt="MongoDB"></p>

<p>This tutorial series will help you start your Rails project with MongoDB.</p>

<p>And I use Mongoid 5.0.0 as an example.</p>

<p>In this tutorial, you will be able to see how to</p>

<ol>
<li><p>Install MongoDB in Mac OSX</p></li>
<li><p>Create Some Database Users in MongoDB</p></li>
<li><p>Setup Rails Projects</p></li>
</ol>


<p>(Tips)</p>

<ol>
<li><p>Dump Data</p></li>
<li><p>Restore Data</p></li>
</ol>


<p>Let&#8217;s go !</p>

<!--more-->


<h2>Install MongoDB in Mac OSX</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># Intall Homebrew (Mac OSX package manager)
</span><span class='line'>
</span><span class='line'>ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
</span><span class='line'>
</span><span class='line'># Update the Hombrew packages first
</span><span class='line'>
</span><span class='line'>brew update
</span><span class='line'>
</span><span class='line'># Install MongoDB
</span><span class='line'>
</span><span class='line'>brew install MongoDB</span></code></pre></td></tr></table></div></figure>


<p>If you want to see where MongoDB is installed</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>brew ls mongodb</span></code></pre></td></tr></table></div></figure>


<h2>Create Some Database Users in MongoDB</h2>

<p>Since I want to enable access control on a MongoDB instance, just like Mysql, I have to use username and password to access MongoDB.</p>

<p>First, you must to know where you can setup MongoDB.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># In MacOSX
</span><span class='line'>
</span><span class='line'>vim /usr/local/etc/mongod.conf
</span><span class='line'>
</span><span class='line'># In Ubuntu 
</span><span class='line'>
</span><span class='line'>vim /etc/mongod.conf</span></code></pre></td></tr></table></div></figure>


<p></p>

<p>Second, you have to make sure that authorization is disable.</p>

<p>In MacOSX</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># /usr/local/etc/mongod.conf
</span><span class='line'>
</span><span class='line'>systemLog:
</span><span class='line'>  destination: file
</span><span class='line'>  path: /usr/local/var/log/mongodb/mongo.log
</span><span class='line'>  logAppend: true
</span><span class='line'>storage:
</span><span class='line'>  dbPath: /usr/local/var/mongodb
</span><span class='line'>net:
</span><span class='line'>  bindIp: 127.0.0.1
</span><span class='line'>security:
</span><span class='line'>  authorization: disabled </span></code></pre></td></tr></table></div></figure>


<p>In Ubuntu</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># /etc/mongod.conf
</span><span class='line'>
</span><span class='line'>...
</span><span class='line'>
</span><span class='line'>noauth = true
</span><span class='line'>...
</span></code></pre></td></tr></table></div></figure>


<p>Then (re)start MongoDB</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># In Ubuntu
</span><span class='line'>sudo service mongod restart
</span><span class='line'>
</span><span class='line'># In Mac OSX
</span><span class='line'>mongod --config /usr/local/etc/mongod.conf</span></code></pre></td></tr></table></div></figure>


<p>Ok, now you are a super user, you can access any database and perform any action. let&#8217;s create an administrator named &#8216;siteUserAdmin&#8217; and he has &#8220;userAdminAnyDatabase&#8221; role first.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ mongo
</span><span class='line'>$ use admin
</span><span class='line'>$ db.createUser(
</span><span class='line'> {
</span><span class='line'>  user: 'siteUserAdmin',
</span><span class='line'>  pwd: '1234567890',
</span><span class='line'>  roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
</span><span class='line'> }
</span><span class='line'>)</span></code></pre></td></tr></table></div></figure>


<p>Ok, now we can enable authorization mode.</p>

<p>In MacOSX</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># vim /usr/local/etc/mongod.conf
</span><span class='line'>
</span><span class='line'>...
</span><span class='line'>
</span><span class='line'>security:
</span><span class='line'>  authorization: enabled </span></code></pre></td></tr></table></div></figure>


<p>In Ubuntu</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># /etc/mongod.conf
</span><span class='line'>
</span><span class='line'>...
</span><span class='line'>
</span><span class='line'>auth = true
</span><span class='line'>...
</span></code></pre></td></tr></table></div></figure>


<p>And don&#8217;t forgot restart MongoDB.</p>

<p>Then we now have to use user name and password to access database.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mongo --host localhost --port 27017 --username siteUserAdmin --password  --authenticationDatabase admin</span></code></pre></td></tr></table></div></figure>


<p>Now, we will create an user  named &#8216;dbadmin&#8217; who has &#8216;dbOwner&#8217; role.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># assume your project will use database: 'your_awesome_project_development'
</span><span class='line'>
</span><span class='line'>use your_awesome_project_development
</span><span class='line'>
</span><span class='line'>db.createUser(
</span><span class='line'>    {
</span><span class='line'>        user: 'dbadmin',
</span><span class='line'>        pwd: '1234567890',
</span><span class='line'>        roles: [ { role: "dbOwner", db: "your_awesome_project_development" } ]
</span><span class='line'>    }
</span><span class='line'>)</span></code></pre></td></tr></table></div></figure>


<p>if you want to update user&#8217;s setting.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>db.updateUser(
</span><span class='line'>    "dbadmin",
</span><span class='line'>    {
</span><span class='line'>        pwd: 'aloha',
</span><span class='line'>        roles: 
</span><span class='line'>          [
</span><span class='line'>              {role: "read", db: "your_awesome_project_development"}
</span><span class='line'>          ]
</span><span class='line'>    }
</span><span class='line'>)</span></code></pre></td></tr></table></div></figure>


<p>if you want to know which roles can perform which  actions, you can find anwser here:</p>

<p><a href="http://docs.mongodb.org/master/reference/built-in-roles/#userAdmin">http://docs.mongodb.org/master/reference/built-in-roles/#userAdmin</a></p>

<h2>Setup Rails Projects</h2>

<h3>1. create a project without active-record</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>rails new your_awesome_project --skip-active-record</span></code></pre></td></tr></table></div></figure>


<h3>2. add Mongoid to Gemfile</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># Gemfile
</span><span class='line'>
</span><span class='line'>gem 'mongoid', '~&gt; 5.0.0'</span></code></pre></td></tr></table></div></figure>


<h3>3. Create a mongoid.yml</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>bundle install
</span><span class='line'>rails g mongoid:config</span></code></pre></td></tr></table></div></figure>


<h3>4. Update yout mongoid.yml</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>development:
</span><span class='line'>  clients:
</span><span class='line'>    default:
</span><span class='line'>      database: your_awesome_project_development
</span><span class='line'>      hosts:
</span><span class='line'>        - localhost:27017
</span><span class='line'>      options:
</span><span class='line'>        user: 'dbadmin'
</span><span class='line'>        password: '1234567890'
</span><span class='line'>        roles:
</span><span class='line'>          - 'dbOwner'
</span><span class='line'>test:
</span><span class='line'>  clients:
</span><span class='line'>    default:
</span><span class='line'>      database: your_awesome_project_test
</span><span class='line'>      hosts:
</span><span class='line'>        - localhost:27017
</span><span class='line'>      options:
</span><span class='line'>        max_retries: 1
</span><span class='line'>        retry_interval: 0
</span><span class='line'>        user: 'dbadmin_tester'
</span><span class='line'>        password: '1234567890'
</span><span class='line'>        roles:
</span><span class='line'>          - 'dbOwner'</span></code></pre></td></tr></table></div></figure>


<h2>Dump Data</h2>

<p>Please make sure that you have backup role in production MongoDB.</p>

<p>I use &#8216;readWriteAnyDatabase&#8217; role.</p>

<p>Assume that you need to dump data from production data and restore to local mongo server.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mongodump --host your.production.mongo.server.ip --port 37017 --username user --password --out /Users/AlohaCC/Desktop/production-mongodump-2015-10-04</span></code></pre></td></tr></table></div></figure>


<h2>Restore Data</h2>

<p>Please make sure that you have backup role in local MongoDB.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mongorestore --host localhost --port 3017 --username user --password  /Users/AlohaCC/Desktop/production-mongodump-2015-10-04</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[[Tips] Limit Class Only be used in Development Environment]]></title>
    <link href="http://ccaloha.cc/blog/2015/08/27/tips-limit-some-class-only-be-used-in-development-environment/"/>
    <updated>2015-08-27T17:22:50+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/08/27/tips-limit-some-class-only-be-used-in-development-environment</id>
    <content type="html"><![CDATA[<p>In my project, I have to dump data from Mysql to MongoDB. And, this feature only used in development. However, Rails will load any classes under folder &#8220;app/&#8221; automatically . So, how can I avoid &#8220;Development-Only Class&#8221; loading in production environment?</p>

<p>let&#8217;s check it out~</p>

<p>If you have a class named: <strong>DumpDataFromOldServer</strong> and it located at  folder <strong>&#8216;/app/development_only&#8217;</strong>. like this:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># app/development_only/dump_data_from_old_server.rb
</span><span class='line'>
</span><span class='line'>class DumpDataFromOldServer &lt; ActiveRecord::Base
</span><span class='line'>    ....
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<h2>Step1. Application.rb</h2>

<p>you still need to let Rails know which folders should be loaded by using <strong>config.eager_load_paths</strong>.</p>

<p>ps. what&#8217;s difference between eager_load and auto_load?</p>

<p>please check this <a href="http://stackoverflow.com/questions/19773266/confusing-about-autoload-paths-vs-eager-load-paths-in-rails-4">http://stackoverflow.com/questions/19773266/confusing-about-autoload-paths-vs-eager-load-paths-in-rails-4</a></p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'> config.eager_load_paths += %W(#{Rails.root}/app/development_only)</span></code></pre></td></tr></table></div></figure>


<p>It will be:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># config/application.rb
</span><span class='line'>
</span><span class='line'>require File.expand_path('../boot', __FILE__)
</span><span class='line'>
</span><span class='line'>Bundler.require(*Rails.groups)
</span><span class='line'>
</span><span class='line'>module MyAwesomeProject
</span><span class='line'>  class Application &lt; Rails::Application
</span><span class='line'>   ...
</span><span class='line'>   
</span><span class='line'>   config.eager_load_paths += %W(#{Rails.root}/app/development_only)
</span><span class='line'>   
</span><span class='line'>   ...
</span><span class='line'>   end 
</span><span class='line'>end
</span></code></pre></td></tr></table></div></figure>


<h2>Step2. Update your environment file</h2>

<p>ref: <a href="http://stackoverflow.com/questions/13756986/how-to-blacklist-directory-loading-in-rails">http://stackoverflow.com/questions/13756986/how-to-blacklist-directory-loading-in-rails</a></p>

<p>Add these lines</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>  path_rejector = lambda { |s| s.include?("app/development_only") }
</span><span class='line'>
</span><span class='line'>  # Remove the path from being loaded when Rails starts:
</span><span class='line'>  config.eager_load_paths = config.eager_load_paths.reject(&path_rejector)
</span><span class='line'>
</span><span class='line'>  # Remove the path from being lazily loaded
</span><span class='line'>  ActiveSupport::Dependencies.autoload_paths.reject!(&path_rejector)</span></code></pre></td></tr></table></div></figure>


<p>It will be:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># config/environments/production.rb
</span><span class='line'>Rails.application.configure do
</span><span class='line'>  
</span><span class='line'>  ...
</span><span class='line'>
</span><span class='line'>  path_rejector = lambda { |s| s.include?("app/development_only") }
</span><span class='line'>
</span><span class='line'>  config.eager_load_paths = config.eager_load_paths.reject(&path_rejector)
</span><span class='line'>
</span><span class='line'>  ActiveSupport::Dependencies.autoload_paths.reject!(&path_rejector)
</span><span class='line'>end
</span></code></pre></td></tr></table></div></figure>


<h2>Step3. Test</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># In Development console: rails c 
</span><span class='line'>
</span><span class='line'>&gt; DumpDataFromOldServer.all.first 
</span><span class='line'>
</span><span class='line'>DumpDataFromOldServer Load (0.6ms)  SELECT  `dump_data_from_old_server`.* FROM `dump_data_from_old_server `  LIMIT 1
</span><span class='line'>=&gt; #&lt;DumpDataFromOldServer id: 1, created_at: "2014-05-09 16:59:55"&gt;
</span><span class='line'>
</span><span class='line'>
</span><span class='line'># In Production console: rails c -e production
</span><span class='line'>&gt; DumpDataFromOldServer.all.first
</span><span class='line'>NameError: uninitialized constant DumpDataFromOldServer
</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Build up a homemade server-to-server interactions with Google API using Ruby - Take Google Analytics As An Example]]></title>
    <link href="http://ccaloha.cc/blog/2015/08/23/build-up-a-homemade-server-to-server-interactions-with-google-api-using-ruby-take-google-analytics-as-an-example/"/>
    <updated>2015-08-23T15:10:37+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/08/23/build-up-a-homemade-server-to-server-interactions-with-google-api-using-ruby-take-google-analytics-as-an-example</id>
    <content type="html"><![CDATA[<p>Since &#8216;google-api-ruby-client&#8217; make a big change from <a href="https://github.com/google/google-api-ruby-client/blob/master/MIGRATING.md">0.8 to 0.9</a> when my project is running out of time, and also I met this <a href="https://github.com/google/google-api-ruby-client/issues/253">issue</a>, So, I make this decision, let&#8217;s build a API client by myself.</p>

<p>This implementation refer to Google <a href="https://developers.google.com/identity/protocols/OAuth2ServiceAccount">guideline</a></p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/build%20up%20server-to-server%20interactions%20with%20Google%20API/Google%20API%20server-to-server.png"></p>

<!--more-->


<h2>Step1. Preparation</h2>

<h3>1. Create a Service Account</h3>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/build%20up%20server-to-server%20interactions%20with%20Google%20API/Add%20Credentials.png" alt="add credential"></p>

<h3>2. Download JSON file</h3>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/build%20up%20server-to-server%20interactions%20with%20Google%20API/Download%20JSON.png" alt="Download JSON File"></p>

<p>And I put JSON file into <strong>config</strong> folder.</p>

<h3>3. Copy a Email and Add to Google Analytics</h3>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/build%20up%20server-to-server%20interactions%20with%20Google%20API/Remember%20your%20Email%20.png"></p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/build%20up%20server-to-server%20interactions%20with%20Google%20API/Add%20email%20to%20Google%20Analytics%20by%20User%20Management.png" alt="Add generated email to Google analytics user"></p>

<h2>Step2. Get Access Token</h2>

<p>Add to <strong>Gemfile</strong></p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>gem "typhoeus"
</span><span class='line'>gem 'jwt'</span></code></pre></td></tr></table></div></figure>


<p>Create a file named <strong>lib/google_anallytics.rb</strong></p>

<p>Again, this implementation refer to Google <a href="https://developers.google.com/identity/protocols/OAuth2ServiceAccount">guideline</a>. Please, check this guideline first. Then you will understand how I write fuction get_token.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>require 'typhoeus'
</span><span class='line'>require "jwt"
</span><span class='line'>require 'date'
</span><span class='line'>module GoogleAnalytic
</span><span class='line'> 
</span><span class='line'>  def get_token
</span><span class='line'>    json_file = File.join("#{Rails.root}/config", 'YOUR-JSON-FILE-NAME.json')
</span><span class='line'>    file = File.read(json_file)
</span><span class='line'>    key_hash = JSON.parse(file)
</span><span class='line'>    aud     = "https://www.googleapis.com/oauth2/v3/token"
</span><span class='line'>    now     = Time.new
</span><span class='line'>    iat     = (now - 60).to_i
</span><span class='line'>    @exp    = (now + 50.minutes).to_i
</span><span class='line'>    scopes  = 'https://www.googleapis.com/auth/analytics.readonly'
</span><span class='line'>    sub     = key_hash["client_email"]
</span><span class='line'>    iss     = key_hash["client_email"]
</span><span class='line'>    jwt_claim_set    = {
</span><span class='line'>      'iss' =&gt; iss, 
</span><span class='line'>      'sub' =&gt; sub, 
</span><span class='line'>      'scope' =&gt; scopes,
</span><span class='line'>      'aud' =&gt; aud, 
</span><span class='line'>      'exp' =&gt; @exp,
</span><span class='line'>      'iat' =&gt; iat
</span><span class='line'>      }
</span><span class='line'>    token            = JWT.encode(jwt_claim_set,  OpenSSL::PKey::RSA.new(key_hash["private_key"]), 'RS256')
</span><span class='line'>    
</span><span class='line'>    res = Typhoeus::Request.post('https://www.googleapis.com/oauth2/v3/token', 
</span><span class='line'>      body: 
</span><span class='line'>        { 
</span><span class='line'>          grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
</span><span class='line'>          assertion: token
</span><span class='line'>        }
</span><span class='line'>    )
</span><span class='line'>    
</span><span class='line'>    res_hash = MultiJson.load(res.body)
</span><span class='line'>
</span><span class='line'>    token = res_hash["access_token"]
</span><span class='line'>    return token
</span><span class='line'>  end
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<h2>Step3. Fetch Data</h2>

<p>In my case, I create a model for saving Google Analytics data. So, I&#8217;ll show you how I fetch data.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
<span class='line-number'>55</span>
<span class='line-number'>56</span>
<span class='line-number'>57</span>
<span class='line-number'>58</span>
<span class='line-number'>59</span>
<span class='line-number'>60</span>
<span class='line-number'>61</span>
<span class='line-number'>62</span>
<span class='line-number'>63</span>
<span class='line-number'>64</span>
<span class='line-number'>65</span>
<span class='line-number'>66</span>
<span class='line-number'>67</span>
<span class='line-number'>68</span>
<span class='line-number'>69</span>
<span class='line-number'>70</span>
<span class='line-number'>71</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>class GoogleAnalyticEvent
</span><span class='line'>  include GoogleAnalytic
</span><span class='line'>  include Mongoid::Document
</span><span class='line'>  include Mongoid::Timestamps
</span><span class='line'>  field :category, type: String        
</span><span class='line'>  field :label, type: String        
</span><span class='line'>  field :action, type: String
</span><span class='line'>  field :total_events, type: Integer       
</span><span class='line'>  field :unique_events, type: Integer
</span><span class='line'>  field :data_date, type: Date
</span><span class='line'>  index({category: 1, label: 1, data_date: 1, action:1 })
</span><span class='line'>  
</span><span class='line'>  def get_event_from_ga_api(params={})
</span><span class='line'>    @data_date = Date.today - 1
</span><span class='line'>    @parameters = {
</span><span class='line'>        'ids'         =&gt; "ga:12345678",
</span><span class='line'>        'start-date'  =&gt; @data_date.strftime("%Y-%m-%d"),
</span><span class='line'>        'end-date'    =&gt; @data_date.strftime("%Y-%m-%d"),
</span><span class='line'>        'metrics'     =&gt; "ga:totalEvents,ga:uniqueEvents",
</span><span class='line'>        'dimensions'  =&gt; "ga:eventCategory,ga:eventLabel,ga:eventAction",
</span><span class='line'>        'max-results' =&gt; "10000"
</span><span class='line'>      }
</span><span class='line'>    query_paginate(@parameters)
</span><span class='line'>  end
</span><span class='line'>  
</span><span class='line'>  def query_paginate(parameters)
</span><span class='line'>    parameters['access_token'] = get_token
</span><span class='line'>    count = 0
</span><span class='line'>    loop do
</span><span class='line'>        if Time.new.to_i &gt; @exp
</span><span class='line'>          parameters['access_token'] = get_token
</span><span class='line'>        end
</span><span class='line'>       res = Typhoeus::Request.get('https://www.googleapis.com/analytics/v3/data/ga', 
</span><span class='line'>        params: parameters
</span><span class='line'>       )
</span><span class='line'>      result = MultiJson.load(res.body)
</span><span class='line'>      if result["error"].nil?
</span><span class='line'>        sync_to_db(result)  if result["totalResults"] &gt; 0
</span><span class='line'>        max_count = result["totalResults"] / parameters["max-results"].to_i
</span><span class='line'>      end
</span><span class='line'>      break if count == max_count || result["error"].present?
</span><span class='line'>      count += 1
</span><span class='line'>      parameters["start-index"] = parameters["max-results"].to_i * count + 1
</span><span class='line'>    end
</span><span class='line'>  end
</span><span class='line'>
</span><span class='line'>  def sync_to_db(result)
</span><span class='line'>    result["columnHeaders"].each_with_index do |h,i|
</span><span class='line'>      case h["name"].to_s
</span><span class='line'>      when "ga:eventCategory"
</span><span class='line'>        @category= i 
</span><span class='line'>      when "ga:eventLabel"
</span><span class='line'>        @label   = i
</span><span class='line'>      when "ga:eventAction"
</span><span class='line'>        @action  = i
</span><span class='line'>      when "ga:totalEvents" 
</span><span class='line'>        @total_events  = i
</span><span class='line'>      when "ga:uniqueEvents"
</span><span class='line'>        @unique_events = i
</span><span class='line'>      end
</span><span class='line'>    end 
</span><span class='line'>    result["rows"].each do |array_data|
</span><span class='line'>        GoogleAnalyticEvent.create(category:      array_data[@category], 
</span><span class='line'>                                    label:        array_data[@label], 
</span><span class='line'>                                    action:       array_data[@action],
</span><span class='line'>                                    total_events:       array_data[@total_events],
</span><span class='line'>                                    unique_events:      array_data[@unique_events],
</span><span class='line'>                                    data_date:          @data_date)
</span><span class='line'>    end
</span><span class='line'>  end
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<p>Let&#8217;s me explain each code snapshots.</p>

<h3>3-1: First of all, I initialize query parameters such as ids, start-date, &#8230;etc in instance variable @parameters.</h3>

<p>PS. you can easily understand which parameters you need by using <a href="https://ga-dev-tools.appspot.com/query-explorer/">Google Analytics Query Explorer</a>. Once, you find out which parameters you need, just copy to here like I do.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>def get_event_from_ga_api(params={})
</span><span class='line'>    @data_date = Date.today - 1
</span><span class='line'>    @parameters = {
</span><span class='line'>        'ids'         =&gt; "ga:12345678",
</span><span class='line'>        'start-date'  =&gt; @data_date.strftime("%Y-%m-%d"),
</span><span class='line'>        'end-date'    =&gt; @data_date.strftime("%Y-%m-%d"),
</span><span class='line'>        'metrics'     =&gt; "ga:totalEvents,ga:uniqueEvents",
</span><span class='line'>        'dimensions'  =&gt; "ga:eventCategory,ga:eventLabel,ga:eventAction",
</span><span class='line'>        'max-results' =&gt; "10000"
</span><span class='line'>      }
</span><span class='line'>    query_paginate(@parameters)
</span><span class='line'>  end</span></code></pre></td></tr></table></div></figure>


<h3>3-2: Second, let&#8217;s see sync_to_db first.</h3>

<p>After I got Google API&#8217;s response, I will save to database.</p>

<p>let&#8217;s see what do we get from google analytics first.</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/build%20up%20server-to-server%20interactions%20with%20Google%20API/Google%20Analytics%20Return%20Result.png" alt="Google Analytics Return Data"></p>

<p>Google show you the return data&#8217;s column header name by &#8220;columnHeaders&#8221;. And &#8220;rows&#8221; data is given as an array. And each array&#8217;s elements refer to &#8220;columnHeaders&#8221; respectively. For exameple, in above picture, you can see &#8220;ga:eventCategory&#8221; is in first of columnHeaders. So, in rows, each array&#8217;s first element is value of &#8220;ga:eventCategory&#8221;. Then I save data to model GoogleAnalyticEvent.</p>

<p>That&#8217;s all fuction &#8220;sync_to_db&#8221; do.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>def sync_to_db(result)
</span><span class='line'>    result["columnHeaders"].each_with_index do |h,i|
</span><span class='line'>      case h["name"].to_s
</span><span class='line'>      when "ga:eventCategory"
</span><span class='line'>        @category= i 
</span><span class='line'>      when "ga:eventLabel"
</span><span class='line'>        @label   = i
</span><span class='line'>      when "ga:eventAction"
</span><span class='line'>        @action  = i
</span><span class='line'>      when "ga:totalEvents" 
</span><span class='line'>        @total_events  = i
</span><span class='line'>      when "ga:uniqueEvents"
</span><span class='line'>        @unique_events = i
</span><span class='line'>      end
</span><span class='line'>    end 
</span><span class='line'>    result["rows"].each do |array_data|
</span><span class='line'>        GoogleAnalyticEvent.create(category:      array_data[@category], 
</span><span class='line'>                                    label:        array_data[@label], 
</span><span class='line'>                                    action:       array_data[@action],
</span><span class='line'>                                    total_events:       array_data[@total_events],
</span><span class='line'>                                    unique_events:      array_data[@unique_events],
</span><span class='line'>                                    data_date:          @data_date)
</span><span class='line'>    end
</span><span class='line'>  end</span></code></pre></td></tr></table></div></figure>


<h3>3-3: Finally, let&#8217;s see function query_paginate.</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>def query_paginate(parameters)
</span><span class='line'>    parameters['access_token'] = get_token
</span><span class='line'>    count = 0
</span><span class='line'>    loop do
</span><span class='line'>        if Time.new.to_i &gt; @exp
</span><span class='line'>          parameters['access_token'] = get_token
</span><span class='line'>        end
</span><span class='line'>       res = Typhoeus::Request.get('https://www.googleapis.com/analytics/v3/data/ga', 
</span><span class='line'>        params: parameters
</span><span class='line'>       )
</span><span class='line'>      result = MultiJson.load(res.body)
</span><span class='line'>      if result["error"].nil?
</span><span class='line'>        sync_to_db(result)  if result["totalResults"] &gt; 0
</span><span class='line'>        max_count = result["totalResults"] / parameters["max-results"].to_i
</span><span class='line'>      end
</span><span class='line'>      break if count == max_count || result["error"].present?
</span><span class='line'>      count += 1
</span><span class='line'>      parameters["start-index"] = parameters["max-results"].to_i * count + 1
</span><span class='line'>    end
</span><span class='line'>  end</span></code></pre></td></tr></table></div></figure>


<p>I have to get access token by &#8220;get_token&#8221;, And this function also assign expire time to @exp.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>parameters['access_token'] = get_token
</span></code></pre></td></tr></table></div></figure>


<p>Then I write a loop for pagination. At first, I check token is expired or not, if is expired, I have to get token again.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>if Time.new.to_i &gt; @exp
</span><span class='line'>  parameters['access_token'] = get_token
</span><span class='line'>end
</span></code></pre></td></tr></table></div></figure>


<p>Now, I use Typhoeus::Request.get method with params fetching data from Google Analytics.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>res = Typhoeus::Request.get('https://www.googleapis.com/analytics/v3/data/ga', 
</span><span class='line'>        params: parameters
</span><span class='line'>       )
</span><span class='line'>result = MultiJson.load(res.body)</span></code></pre></td></tr></table></div></figure>


<p>And if there are no any errors, I will save data to database.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>if result["error"].nil?
</span><span class='line'>  sync_to_db(result)  if result["totalResults"] &gt; 0
</span><span class='line'>  max_count = result["totalResults"] / parameters["max-results"].to_i
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<p></p>

<p>Finally, break or continue to get next batch data.</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>break if count == max_count || result["error"].present?
</span><span class='line'>count += 1
</span><span class='line'>parameters["start-index"] = parameters["max-results"].to_i * count + 1</span></code></pre></td></tr></table></div></figure>


<h2>That&#8217;s it.</h2>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[For Dummies Stress Test Using JMeter in Rails 4 + Ruby 2.2]]></title>
    <link href="http://ccaloha.cc/blog/2015/07/22/for-dummies-stress-test-using-jmeter-in-rails-4-plus-ruby-2-dot-2/"/>
    <updated>2015-07-22T09:16:12+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/07/22/for-dummies-stress-test-using-jmeter-in-rails-4-plus-ruby-2-dot-2</id>
    <content type="html"><![CDATA[<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/stress%20test/jmeter.png" alt="jmeter generated by ruby-jemer"></p>

<!--more-->


<p>Inspired by</p>

<ol>
<li><a href="http://www.jetthoughts.com/blog/tricks/2014/08/05/stress-testing-your-rails-application-using-jmeter.html">http://www.jetthoughts.com/blog/tricks/2014/08/05/stress-testing-your-rails-application-using-jmeter.html</a></li>
<li><a href="https://github.com/flood-io/ruby-jmeter">https://github.com/flood-io/ruby-jmeter</a></li>
<li><a href="http://jmeter.apache.org/usermanual/get-started.html">http://jmeter.apache.org/usermanual/get-started.html</a></li>
</ol>


<h2>In your local machine (Mac OSX)</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>brew update
</span><span class='line'>brew install jmeter --with-plugins
</span></code></pre></td></tr></table></div></figure>


<h2>In your remote server</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># if your want test in remote server
</span><span class='line'>sudo apt-get install jmeter 
</span></code></pre></td></tr></table></div></figure>


<h2>Gemfile</h2>

<p>write test plans for JMeter easily</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>gem ruby-jmeter
</span></code></pre></td></tr></table></div></figure>


<p></p>

<h2>寫Test Plan</h2>

<p>[Rereference] 語法支援哪些：</p>

<ol>
<li><p><a href="https://github.com/flood-io/ruby-jmeter/blob/5ae25cc32c8d05fcbe32bf143bdfbbd2d657517c/lib/ruby-jmeter/DSL.md">https://github.com/flood-io/ruby-jmeter/blob/5ae25cc32c8d05fcbe32bf143bdfbbd2d657517c/lib/ruby-jmeter/DSL.md</a></p></li>
<li><p>可查看 alies_methods <a href="https://github.com/flood-io/ruby-jmeter/blob/5ae25cc32c8d05fcbe32bf143bdfbbd2d657517c/lib/ruby-jmeter/dsl.rb">https://github.com/flood-io/ruby-jmeter/blob/5ae25cc32c8d05fcbe32bf143bdfbbd2d657517c/lib/ruby-jmeter/dsl.rb</a></p></li>
</ol>


<h3>Write A Stress Test Helper</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># test/stress/stress_helper.rb
</span><span class='line'>
</span><span class='line'>require 'ruby-jmeter'
</span><span class='line'>
</span><span class='line'>def default_setting(params={})
</span><span class='line'>  domain    = params[:domain]        || 'localhost' 
</span><span class='line'>  protocol  = params[:protocol]      || 'https'
</span><span class='line'>  port      = params[:port]          || '3000'
</span><span class='line'>  cookie    = params[:cookie].to_s   || 'true'
</span><span class='line'>  # defaults 會建立jmeter的 "HTTP Request Default"，讓你設定 domain或是 protocol...等數值
</span><span class='line'>  defaults domain: domain, protocol: protocol, port: port
</span><span class='line'>
</span><span class='line'>  # cookies 會建立jmeter的 "HTTP Cookie Manager"
</span><span class='line'>  # 如果希望每個threads都有自己的cookie，要將此行code，寫到thread內
</span><span class='line'>  # cookies policy 之前卡了我很久，見註一
</span><span class='line'>  cookies policy: 'compatibility',clear_each_iteration: true unless params[:cookie] == 'false'
</span><span class='line'>end
</span><span class='line'>
</span><span class='line'># Login Helper 
</span><span class='line'># 因為我們登入後會倒回首頁，所以，我有檢查 使用者的名稱是否有出現
</span><span class='line'>def login(email,pwd,nickname)
</span><span class='line'>  visit name: 'Sign In', url: '/users/sign_in' do
</span><span class='line'>    # 會抓變數，並且將值assign給 'csrf-token' 與 'csrf-param'
</span><span class='line'>    extract name: 'csrf-token', xpath: "//meta[@name='csrf-token']/@content", tolerant: true
</span><span class='line'>    extract name: 'csrf-param', xpath: "//meta[@name='csrf-param']/@content", tolerant: true
</span><span class='line'>  end
</span><span class='line'>
</span><span class='line'>  # For devise issue
</span><span class='line'>  http_header_manager name: 'X-CSRF-Token', value: '${csrf-token}'
</span><span class='line'>
</span><span class='line'>  # 送出登入
</span><span class='line'>  submit name: 'Submit Sign In form', url: '/users/sign_in',
</span><span class='line'>    always_encode: true,
</span><span class='line'>    fill_in: {
</span><span class='line'>      'utf8'          =&gt; '✓',
</span><span class='line'>      '${csrf-param}' =&gt; '${csrf-token}',
</span><span class='line'>      'user[email]'        =&gt; email,
</span><span class='line'>      'user[password]'     =&gt; pwd,
</span><span class='line'>      'commit'             =&gt; 'Log in'
</span><span class='line'>    } do
</span><span class='line'>    # 登入後會倒回首頁，檢查是否包含你的nickname
</span><span class='line'>    assert contains: nickname, scope: 'main'
</span><span class='line'>  end
</span><span class='line'>end</span></code></pre></td></tr></table></div></figure>


<p>註一： cookies policy 之前卡了我很久，因為我發現我登入都會錯誤！都是csrf-token 不相符，我的認知，那是因為Devise使用cookie作為判斷機制，所以cookie policy不能選用<strong>rfc2109</strong>，要選用<strong>compatibility</strong> 他會符合目前幾乎所有線上網站的cookie需求！</p>

<p>Ruby-Jmeter &lt;-> Jmeter</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/stress%20test/ruby-jmeter-mapping-1.png" alt="ruby-jmeter-mapping-1"></p>

<h3>Non Login User Stress Test Plan</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
<span class='line-number'>55</span>
<span class='line-number'>56</span>
<span class='line-number'>57</span>
<span class='line-number'>58</span>
<span class='line-number'>59</span>
<span class='line-number'>60</span>
<span class='line-number'>61</span>
<span class='line-number'>62</span>
<span class='line-number'>63</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># test/stress/non_login.rb
</span><span class='line'>
</span><span class='line'>require './stress_helper' 
</span><span class='line'> 
</span><span class='line'>test do
</span><span class='line'>  default_setting domain: 'your.awesome.website', portocal: 'https' , port: '443'
</span><span class='line'>  #default_setting() # 本機測試使用
</span><span class='line'>  
</span><span class='line'>  # 壓測，登入後的行為
</span><span class='line'>  # * 使用threads是因為 thread 代表jmeter裡面的thread_group，thread_group就是告訴jmeter
</span><span class='line'>  #   有多少使用者要模擬，多久送一次requests，多少requests要被送出
</span><span class='line'>  # * counts 代表 多少個使用者
</span><span class='line'>  # * rampup 每個使用者的執行間隔
</span><span class='line'>  # * loops 代表多重複做多少次測試
</span><span class='line'>  
</span><span class='line'>  for i in 1..10 
</span><span class='line'>    threads count: 20, rampup: 1 ,loops: 1 do
</span><span class='line'>      think_time 5000, 2000
</span><span class='line'>        transaction "活動專區#{i}" do
</span><span class='line'>          # 這邊會去爬 class: 'items' 底下每個 &lt;a&gt; 的href
</span><span class='line'>        # 並且把他assign給 "act-urls"
</span><span class='line'>          visit name: '活動專區首頁', url: "/activities?sort=1&page=#{Random.rand(1..4)}" do 
</span><span class='line'>            extract name: 'act-urls', xpath: "//div[contains(@class,'items')]//a//@href", tolerant: true 
</span><span class='line'>          end
</span><span class='line'>          for i in 1..3
</span><span class='line'>            visit name: "隨便點活動#{i}", url: "${act-urls_#{Random.rand(1..12)}}" do
</span><span class='line'>              extract name: 'product-urls', xpath: "//div[contains(@class,'item-info')]//a//@href", tolerant: true 
</span><span class='line'>            end
</span><span class='line'>          end
</span><span class='line'>        end
</span><span class='line'>      view_results_tree
</span><span class='line'>      debug_sampler
</span><span class='line'>    end
</span><span class='line'>  end 
</span><span class='line'>
</span><span class='line'>  for i in 1..5
</span><span class='line'>    threads count: 20, rampup: 1 ,loops: 1 do
</span><span class='line'>      think_time 5000, 2000
</span><span class='line'>        transaction "活動專區-標籤#{i}" do
</span><span class='line'>          visit name: '活動專區首頁', url: "/activities" do 
</span><span class='line'>            extract name: 'act-tag-urls', xpath: "//div[contains(@class,'keywords')]//a//@href", tolerant: true 
</span><span class='line'>          end
</span><span class='line'>          for i in 1..4
</span><span class='line'>            visit name: "隨便點活動標籤#{i}", url: "${act-tag-urls_#{Random.rand(1..4)}}"
</span><span class='line'>          end
</span><span class='line'>        end
</span><span class='line'>      view_results_tree
</span><span class='line'>      debug_sampler
</span><span class='line'>    end
</span><span class='line'>  end 
</span><span class='line'>
</span><span class='line'>  view_results_in_table
</span><span class='line'>  graph_results
</span><span class='line'>  aggregate_graph
</span><span class='line'>  view_results_tree
</span><span class='line'>  summary_report
</span><span class='line'> 
</span><span class='line'>end.run(
</span><span class='line'>  gui: true,
</span><span class='line'>  file: 'jmeter-visitor.jmx',
</span><span class='line'>  log:  'jmeter-visitor.log',
</span><span class='line'>  jtl:  'results-visit.jtl'
</span><span class='line'>)</span></code></pre></td></tr></table></div></figure>


<p>Ruby-Jmeter &lt;-> Jmeter</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/stress%20test/ruby-jmeter-mapping-2.png" alt="ruby-jmeter-mapping-2"></p>

<h3>Login User Stress Test</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># test/stress/login.rb 
</span><span class='line'>
</span><span class='line'>require './stress_helper' 
</span><span class='line'>
</span><span class='line'>test do
</span><span class='line'>  default_setting domain: 'your.awesome.website', portocal: 'https' , port: '443'
</span><span class='line'>  # default_setting() # 本機測試使用
</span><span class='line'>
</span><span class='line'>  # 壓測，登入後的行為
</span><span class='line'>  threads count: 10, rampup: 1, loops: 1 do
</span><span class='line'>    think_time 5000, 2000
</span><span class='line'> 
</span><span class='line'>    transaction 'Log In and View page' do
</span><span class='line'> 
</span><span class='line'>     login('your@login.account','your-password','your-nickname')
</span><span class='line'>
</span><span class='line'>    # 這邊會去爬 class: 'ucnh-fs-ls-uctrls' 底下每個 &lt;a&gt; 的href
</span><span class='line'>    # 並且把他assign給 "users-urls"
</span><span class='line'>      visit name: 'activity', url: '/activities' do 
</span><span class='line'>        extract name: 'users-urls', xpath: "//div[contains(@class,'ucnh-fs-ls-uctrls')]//a//@href", tolerant: true 
</span><span class='line'>      end
</span><span class='line'>  
</span><span class='line'>  # 因為users-urls是個 array，用法就是 ${users-url_X}, X代表array第X個element
</span><span class='line'>      visit name: 'user list1', url: '${users-urls_1}'
</span><span class='line'>      visit name: 'user lists2', url: '${users-urls_3}'
</span><span class='line'>      visit name: 'user lists3', url: '${users-urls_4}'
</span><span class='line'>      visit name: 'user profile', url: '${users-urls_5}'
</span><span class='line'>      visit name: 'user account edit', url: '${users-urls_6}'
</span><span class='line'>    end
</span><span class='line'>    debug_sampler # 給Debug用
</span><span class='line'>    view_results_tree
</span><span class='line'>  end
</span><span class='line'>  view_results_in_table
</span><span class='line'>  graph_results
</span><span class='line'>  aggregate_graph
</span><span class='line'>  view_results_tree
</span><span class='line'>  summary_report
</span><span class='line'> 
</span><span class='line'>end.run(
</span><span class='line'>  gui: true,
</span><span class='line'>  file: 'jmeter-after-sign-in.jmx',
</span><span class='line'>  log:  'jmeter-after-sign-in.log',
</span><span class='line'>  jtl:  'results-after-sign-in.jtl'
</span><span class='line'>)</span></code></pre></td></tr></table></div></figure>


<p>Ruby-Jmeter &lt;-> Jmeter</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/stress%20test/ruby-jmeter-mapping-3-submit-form.png" alt="ruby-jmeter-mapping-3-submit-form"></p>

<h2>開始測試</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>cd your_awesome_projects/test/stress
</span><span class='line'># ruby 你要執行的.rb檔，ex: 
</span><span class='line'>ruby non_login.rb
</span><span class='line'># 此時會跳出一個apache jmeter的畫面，點選綠色執行按鈕即可
</span></code></pre></td></tr></table></div></figure>


<h2>Others</h2>

<h3>Remove tracking jmeter generated files</h3>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>echo "/test/stress/*.log" &gt;&gt; .gitignore
</span><span class='line'>echo "/test/stress/*.jmx" &gt;&gt; .gitignore
</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[HowTo Performance Test in Rails 4]]></title>
    <link href="http://ccaloha.cc/blog/2015/07/14/howto-performance-test-in-rails-4/"/>
    <updated>2015-07-14T19:35:26+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/07/14/howto-performance-test-in-rails-4</id>
    <content type="html"><![CDATA[<p>Inspired by</p>

<ol>
<li><p><a href="http://tekin.co.uk/2014/09/performance-test-rails-against-real-data/">PERFORMANCE TESTING RAILS AGAINST REAL DATA</a></p></li>
<li><p><a href="http://railscasts.com/episodes/411-performance-testing">Performance Testing</a></p></li>
<li><a href="http://railscasts.com/episodes/368-miniprofiler">http://railscasts.com/episodes/368-miniprofiler</a></li>
</ol>


<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Rails%20performance%20test/rails-perftest%20and%20ruby-prof.png"></p>

<!-- more -->


<h2>First thing you should know</h2>

<p>In Rails 4, performance test functionality was extracted out ! So, you need to add</p>

<pre><code># Gemfile
gem 'rails-perftest' ,group: :benchmark
</code></pre>

<p>And if you want your <strong>rails-perftest</strong> show memory usage. You also need to add</p>

<pre><code># Gemfile
gem 'ruby-prof' ,group: :benchmark
</code></pre>

<p><strong>[! Important !]</strong> In my environment,</p>

<pre><code>rails-4.1.6, ruby-2.2.2.133, x86_64-darwin14
</code></pre>

<p>After doing lots of efforts, I finally can get memory usage info.</p>

<p>But I fount this issue. please check: <a href="https://github.com/ruby-prof/ruby-prof/issues/165">https://github.com/ruby-prof/ruby-prof/issues/165</a>.</p>

<p>And I&#8217;m not sure that my memory usage is correct or not.</p>

<p>But I&#8217;ll still show you how I do these jobs. Maybe after fixing the issue, we can get correct informantion.</p>

<h2>Create a new environment for performance test</h2>

<h3>create a new file:  <strong>config/environments/benchmark.rb</strong></h3>

<p>In fact, all contents are copied from config/environments/production.rb since we need to know performance in real world.</p>

<pre><code>Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # Code is not reloaded between requests.
  config.cache_classes = true

  # Eager load code on boot. This eager loads most of Rails and
  # your application in memory, allowing both threaded web servers
  # and those relying on copy on write to perform better.
  # Rake tasks automatically ignore this option for performance.
  config.eager_load = true

  # Full error reports are disabled and caching is turned on.
  config.consider_all_requests_local       = false
  config.action_controller.perform_caching = true

  # Enable Rack::Cache to put a simple HTTP cache in front of your application
  # Add `rack-cache` to your Gemfile before enabling this.
  # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
  # config.action_dispatch.rack_cache = true

  # Disable Rails's static asset server (Apache or nginx will already do this).
  config.serve_static_assets = false

  # Compress JavaScripts and CSS.
  config.assets.js_compressor = :uglifier
  # config.assets.css_compressor = :sass

  # Do not fallback to assets pipeline if a precompiled asset is missed.
  config.assets.compile = false

  # Generate digests for assets URLs.
  config.assets.digest = true

  # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb

  # Specifies the header that your server uses for sending files.
  # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
  # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx

  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true

  # Set to :debug to see everything in the log.
  config.log_level = :info

  # Prepend all log lines with the following tags.
  # config.log_tags = [ :subdomain, :uuid ]

  # Use a different logger for distributed setups.
  # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)

  # Use a different cache store in production.
  # config.cache_store = :mem_cache_store
  config.cache_store = :dalli_store

  # Enable serving of images, stylesheets, and JavaScripts from an asset server.
  # config.action_controller.asset_host = "http://assets.example.com"

  # Ignore bad email addresses and do not raise email delivery errors.
  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
  # config.action_mailer.raise_delivery_errors = false

  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
  # the I18n.default_locale when a translation cannot be found).
  config.i18n.fallbacks = true

  # Send deprecation notices to registered listeners.
  config.active_support.deprecation = :notify

  # Disable automatic flushing of the log to improve performance.
  # config.autoflush_log = false

  # Use default logging formatter so that PID and timestamp are not suppressed.
  config.log_formatter = ::Logger::Formatter.new

  # Do not dump schema after migrations.
  config.active_record.dump_schema_after_migration = false

end
</code></pre>

<h3>add configuration:  <strong>config/database.yml</strong></h3>

<pre><code>default: &amp;default
  adapter: mysql2
  encoding: utf8
  host: 127.0.0.1
  username: root
  password: your_password

staging:
  &lt;&lt;: *default
  database: your_project_development

development:
  &lt;&lt;: *default
  database: your_project_development

test:
  &lt;&lt;: *default
  database: your_project_test

production:
  &lt;&lt;: *default
  database: your_project_production

benchmark:
  &lt;&lt;: *default
  database: your_project_production 
</code></pre>

<h2>Create a test helper file to setup your benchmark tests:</h2>

<p>create a new file: <strong>test/benchmark_helper.rb</strong></p>

<pre><code>ENV["RAILS_ENV"] = "benchmark"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'rails/performance_test_help'

class ActionDispatch::PerformanceTest
   self.profile_options = { runs: 5, metrics: [:wall_time, :memory,:objects, :gc_runs, :gc_time],
                            output: 'tmp/performance', formats: [:flat, :graph_html, :call_tree, :call_stack] }
end
</code></pre>

<h2>Create a test file</h2>

<pre><code>rails generate performance_test page
</code></pre>

<p>and it will create a file named <strong>page.rb</strong> under <strong>test/performance/</strong></p>

<pre><code>require 'benchmark_helper'

class PageTest &lt; ActionDispatch::PerformanceTest
  test "homepage" do
    get '/'
  end
end
</code></pre>

<h2>Add a rake task for running your real-world benchmarks (optional)</h2>

<p>lib/tasks/test_benchmark.rake</p>

<pre><code>Rake::TestTask.new(:real_world_benchmark =&gt; ['test:benchmark_mode']) do |t|
    t.libs &lt;&lt; 'test'
    t.pattern = 'test/performance/**/*_test.rb'
  end
end
</code></pre>

<h2>Update your Rakefile</h2>

<p>Rails 4 no longer defines db:test:prepare, however, <strong>rails-perftest</strong> still use <strong>test:prepare</strong>. So, we need to workaround.</p>

<pre><code>require File.expand_path('../config/application', __FILE__)

Rails.application.load_tasks

Rake.application.instance_eval do
  # Remove test:prepare
  @tasks['test:benchmark'].prerequisites.shift if @tasks['test:benchmark']
  @tasks['test:profile'].prerequisites.shift if @tasks['test:profile']
end
</code></pre>

<h2>For memory measurement, we need the railsexpress patches.</h2>

<p><a href="https://github.com/skaes/rvm-patchsets">https://github.com/skaes/rvm-patchsets</a></p>

<pre><code>rvm get stable
</code></pre>

<p>But my rvm isn&#8217;t update to date correctly, So I use</p>

<pre><code>cd /tmp
git clone https://github.com/skaes/rvm-patchsets.git
cd rvm-patchsets
./install.sh
</code></pre>

<p>Then I run</p>

<pre><code>rvm reinstall 2.2.2 --patch railsexpress
</code></pre>

<p>After reinstall Ruby, we need to recreate our gemset</p>

<pre><code>cd your_project
rvm gemset create your_project
gem install bundler
bundle install
</code></pre>

<h2>Finally</h2>

<p>These command should work~</p>

<pre><code>RAILS_ENV=benchmark bundle exec rake test:real_world_benchmark
RAILS_ENV=benchmark bundle exec rake test:benchmark
RAILS_ENV=benchmark bundle exec rake test:profile
</code></pre>

<p>Then these command will create some files under <strong>your_project/tmp/performance</strong></p>

<p>PS. Please notice that before performance test, you should make sure that there are no any errors while visit the page.</p>

<h2>Without using &#8220;rails-perftest&#8221;, you might use..</h2>

<h3>if You need detailed information, you can use &#8220;request_profiler&#8221;</h3>

<p>inspired by <a href="https://www.coffeepowered.net/2013/08/02/ruby-prof-for-rails/">https://www.coffeepowered.net/2013/08/02/ruby-prof-for-rails/</a></p>

<p>we can use <strong>request_profiler</strong>, which allows you to profile rack requests using ruby-prof.</p>

<pre><code>gem 'request_profiler'
</code></pre>

<p>And you might need to add this line into <strong>config/enviornments/benchmark.rb</strong></p>

<pre><code>config.middleware.use "Rack::RequestProfiler"
</code></pre>

<p>On every page you want to profile, just add <strong>?profile_request=true</strong></p>

<p>for example, we want to profile &#8220;/&#8221; , just key</p>

<pre><code>https://localhost:3000/?profile_request=true
</code></pre>

<p>then you wiil see file named  <strong>2015-07-14-16-49-19&#8212;profile_request=true.html</strong> under <strong>tmp/performance/</strong></p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Rails%20performance%20test/profile_request.png"></p>

<h3>if You need to quickly and easily find out what is bottleneck,  &#8220;rack-mini-profiler&#8221; is best choice.</h3>

<p><strong>Gemfile</strong></p>

<pre><code>gem 'rack-mini-profiler', require: false  
</code></pre>

<p>Create a new file <strong>config/initializers/rack_profiler.rb</strong></p>

<pre><code>if Rails.env == 'development' || Rails.env == 'benchmark'
  require 'rack-mini-profiler'

  # initialization is skipped so trigger it
  Rack::MiniProfilerRails.initialize!(Rails.application)
end
</code></pre>

<p>Add to <strong>app/controllers/application_controller.rb</strong></p>

<pre><code>before_action :authorize_for_miniprofiler
def authorize_for_miniprofiler
  if Rails.env == 'development' || Rails.env == 'benchmark'
    Rack::MiniProfiler.authorize_request
  end
end
</code></pre>

<p>now you can see this under development and benchmark environments</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Rails%20performance%20test/rack-mini-profiler.png"></p>

<h2>You might want to read</h2>

<ol>
<li><a href="http://code.oneapm.com/ruby/2015/04/08/ruby-profilers/">http://code.oneapm.com/ruby/2015/04/08/ruby-profilers/</a></li>
</ol>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Sign up by Devise and Crop User Avatar then Upload by Carrierwave using Ruby on Rails]]></title>
    <link href="http://ccaloha.cc/blog/2015/06/25/sign-up-by-devise-and-crop-user-avatar-then-upload-by-carrierwave-using-ruby-on-rails/"/>
    <updated>2015-06-25T01:15:45+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/06/25/sign-up-by-devise-and-crop-user-avatar-then-upload-by-carrierwave-using-ruby-on-rails</id>
    <content type="html"><![CDATA[<p>ref:</p>

<ol>
<li> http://railscasts.com/episodes/182-cropping-images-revised</li>
<li> http://stackoverflow.com/questions/12762728/how-to-crop-image-on-upload-with-rails-carrierwave-and-minimagick</li>
<li> https://coderwall.com/p/e9d_ja/using-carrierwave-uploader-for-tableless-model-in-rails</li>
<li> http://stackoverflow.com/questions/24262388/carrierwave-processing-only-after-the-model-has-been-saved-model-id-is-nil</li>
</ol>


<h1>What I done in this article</h1>

<h2>1. Click Upload Image</h2>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Crop%20User%20Avatar%20and%20Upload%20via%20Carrierwave%20while%20creating%20user%20using%20Ruby%20on%20Rails/Step1%20click_upload_image.png" alt="Step 1. click_upload_image"></p>

<h2>2. Choose Image And Preview</h2>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Crop%20User%20Avatar%20and%20Upload%20via%20Carrierwave%20while%20creating%20user%20using%20Ruby%20on%20Rails/Step2%20choose%20image%20and%20preview.png" alt="Step 2. choose image and preview"></p>

<h2>3. Edit Avatar And Preview Image Synchronously</h2>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Crop%20User%20Avatar%20and%20Upload%20via%20Carrierwave%20while%20creating%20user%20using%20Ruby%20on%20Rails/Step3%20edit%20avatar%20and%20preview%20sync.png" alt="Step 3. Edit Avatar And Preview Image Synchronously"></p>

<h2>4. Upload to AWS S3</h2>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Crop%20User%20Avatar%20and%20Upload%20via%20Carrierwave%20while%20creating%20user%20using%20Ruby%20on%20Rails/Step4%20upload%20to%20AWS%20S3.png" alt="Step 4. Upload to AWS S3"></p>

<h2>5. Image Cropped And Uploaded</h2>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Crop%20User%20Avatar%20and%20Upload%20via%20Carrierwave%20while%20creating%20user%20using%20Ruby%20on%20Rails/Step5%20image%20cropped%20and%20uploaded.png" alt="Step 5. Image Cropped And Uploaded"></p>

<!-- more -->


<h1>Steps</h1>

<h2>0. Description</h2>

<p>I use Devise for sign up, Carrierwave for upload image and Jcrop for image selection. There is an User model and user has one UserImage model. Carrierwave uploader is mounted on UserImage. In this tutorial, I&#8217;ll not go through upload things. You can find lots of resources such as <a href="http://railscasts.com/episodes/253-carrierwave-file-uploads">carrierwave-file-uploads</a>. Also, I&#8217;ll not show all devise registration and popup(modal) things. Still, there are many resources on the Internet. Ok, let&#8217;s go.</p>

<h2>1. Gemfile</h2>

<p>I use jcrop for cropping image. So, add jcrop library to <em>Gemfile</em>.</p>

<pre><code>gem 'jcrop-rails-v2'
</code></pre>

<p>ps. don&#8217;t forgot <strong>bundle install</strong></p>

<h2>2. Model</h2>

<p>In User model, I only need to tell that User has a main image and it refer to UserImage model, and user signup form contains UserImage&#8217;s attributes, therefore, User model should accept these nested attributes. And I create a method for building image used in upload form(you can see usage in view)</p>

<p><strong>app/models/user.rb</strong></p>

<pre><code>class User &lt; ActiveRecord::Base
  ...
  has_one :main_image, :class_name =&gt; "::UserImage"
    accepts_nested_attributes_for :main_image, reject_if: :all_blank, allow_destroy: true


    def input_main_image
      self.main_image ||= self.build_main_image
      end
  ...
  end
</code></pre>

<p>UserImage model has &#8220;file&#8221; attribute which is used for upload, and we use carrierwave as our uploader, we need to mount this uploader to &#8220;file&#8221; attribute. <strong>[!! Important !!] I guess due to Rails work flow, when UserImageUploader upload image, the UserImage custom attributes: crop_x, crop_y, crop_w and crop_h will not available to UserImageUploader that we can&#8217;t crop image as we want. So we need to skip upload callback first, and we will upload after user is saved.</strong></p>

<p>Ps. if your attribute used for mount uploader named &#8220;hello&#8221;,</p>

<pre><code>mount_uploader :file, UserImagesUploader
skip_callback :save, :after, :store_file!
</code></pre>

<p>will be</p>

<pre><code>mount_uploader :hello, UserImagesUploader
skip_callback :save, :after, :store_hello!
</code></pre>

<p><strong>app/models/user_image.rb</strong></p>

<pre><code># == Schema Information
#
# Table name: user_images
#
#  id                 :integer          not null, primary key
#  user_id            :integer
#  file               :string(255)
#  created_at         :datetime
#  updated_at         :datetime
#

class UserImage &lt; ActiveRecord::Base
  belongs_to :user

    # Used for user avatar image edit
    attr_accessor :crop_x, :crop_y, :crop_w, :crop_h

    mount_uploader :file, UserImagesUploader

    # We need to upload after all attributes assigned，so we skip upload callback first
    skip_callback :save, :after, :store_file!  
end
</code></pre>

<h2>3. View</h2>

<p>In first picture, we have a form for sign up. Due to I have to upload an user image, we need to build it first. Then, UserImage&#8217;s attributes need to be filled. And I will use some javascript methods for editing so I write some div elements such as &#8220;fake-button-upload&#8221;, &#8220;fake-button-edit&#8221;. Also, I need to popup a image for user editing. Below &#8220;.uploaded&#8221; codes are used for popup. Finally, you can see I setup some ids, yap~ for javascript.</p>

<p><strong>app/views/users/registrations/new.html.slim</strong></p>

<pre><code>.upload_personal_image
  = f.fields_for :main_image, resource.input_main_image do |build|
    = build.file_field :file, class: "personal-image-upload-file"
    .preview-outer
      = image_tag(resource.main_image.file, id: "preview") if resource.main_image
    .personal-image-upload.icon-default-photo-150x150
    .fake-button-upload 上傳個人照
    .fake-button-edit 編輯大頭照

    .uploaded
      .edit-title 編輯個人照
      = image_tag(resource.main_image.file, id: "preview-edit") if resource.main_image
      .uploaded-image-edit-attr
        - for attribute in [:crop_x, :crop_y, :crop_w, :crop_h]
          = build.text_field attribute, :id =&gt; attribute
      .edit-desc 請將想要顯示的範圍拖曳至中心圓圈處！
      .fake-button-edit-ok 設為個人照
</code></pre>

<h2>4. CSS</h2>

<p><strong>app/assets/stylesheets/front/users/registrations.css.coffee</strong></p>

<p>First we need to require jquery.Jcrop. And, I hope that our crop selector is a circle so I add&#8230;.</p>

<pre><code>.jcrop-holder div
{
    -webkit-border-radius: 50% !important;
    -moz-border-radius: 50% !important;
    border-radius: 50% !important;
    margin: 0 auto;
    // opacity: 0.6 !important;
}
</code></pre>

<p>You can ignore them but you should notice that some classes are display none such as .personal-image-upload-file,  .preview-outer and .uploaded. These classed will show by javascript control.</p>

<pre><code>//=require jquery.Jcrop

...

.jcrop-holder div
{
    -webkit-border-radius: 50% !important;
    -moz-border-radius: 50% !important;
    border-radius: 50% !important;
    margin: 0 auto;
}

.personal-image-upload-file {
    display: none;
  }

  .preview-outer {
    overflow: hidden;
      display: none;
      width: 150px;
      height: 150px;
      position: absolute;
      left: 0px;
      top: 0px;
      #preview{
        width: 150px;
        height: 150px;
      }
   }
   .uploaded {
  background-color: #FFFFFF;
  text-align: center;
  display: none;
  .edit-title {
    font-size: 20px;
    margin: 0px auto 20px auto;
    padding-top: 30px;
  }
  img {
    border: 1px dashed #dd406f;
    z-index: 2;
  }
  .edit-desc{
    margin-top: 10px;
    margin-bottom: 10px;
    font-size: 14px;
  }

  .uploaded-image-edit-attr{
    display: none;
  }
  .fake-button-edit-ok{
    width: 100px;
    height: 30px;
    font-size: 16px;
    display: inline-block;
    line-height: 30px;
    cursor: pointer;
    background-color: #DD406F;
    border-radius: 3px;
    color: #FFFFFF;
    text-decoration: none;
    margin-bottom: 30px;
  }
}
</code></pre>

<h2>5. Javascript -> most important part for cropping image and previewing image.</h2>

<p>First, we need to require <strong>jquery.Jcrop</strong></p>

<pre><code>#= require jquery.Jcrop
</code></pre>

<p>If user click .personal-image-upload icon or .fake-button-upload button, I&#8217;ll trigger real upload file click.</p>

<pre><code>$('.personal-image-upload').click -&gt; 
  $('.personal-image-upload-file').trigger('click')

$('.fake-button-upload').click -&gt;
  $('.personal-image-upload-file').trigger('click')
</code></pre>

<p>How to preview image?</p>

<p>First, I have to know original image size since if user don&#8217;t resize image, I still need to send crop_x, crop_y, crop_w and crop_h to carrierwave crop function. Second, after FileReader read an image, I&#8217;ll set file path to element #preview and #preview-edit src attribute. Then, I&#8217;ll hide upload icon and show edit button.</p>

<pre><code>initPreviewImage = -&gt;    
    readURL = (input) -&gt;
      if input.files and input.files[0]
        reader = new FileReader

        reader.onload = (e) -&gt;
          img = new Image
          img.onload = -&gt;
            $('#crop_x').val(0);
            $('#crop_y').val(0);
            $('#crop_w').val(img.width);
            $('#crop_h').val(img.height);
          img.src = e.target.result

          $('.preview-outer').css("display": "inline-block")
          $('#preview').attr 'src', e.target.result
          $('#preview-edit').attr 'src', e.target.result
          $('.personal-image-upload').hide()
          $('.fake-button-edit').css("display": "block")
          $('.fake-button-edit').css("right": "0px")
          $('.fake-button-upload').css("left": "0px")

        reader.readAsDataURL input.files[0]

    $('#user_main_image_attributes_file').change -&gt;
      readURL this
 initPreviewImage();
</code></pre>

<p>At last, the cropping part. I setup three global variables for jcrop_api and image size(boundx, boundy). When the element .fake-button-edit button is clicked, I will call showUserImageEdit() and call my modal method for popup function. In showUserImageEdit function, if jcrop_api already existed(user choose a file already), I have to use jcrop_api to reset new image. And #preview-edit is used for binding jcrop, when user select new area or move selection, it will  call update_crop function to update crop_x, crop_y, crop_w and crop_h(don&#8217;t forgot these attributes will send to carrierwave uploaded for cropping) and I will update #preview image as well.</p>

<p>Ps. you might ask why rx = 150 / coords.w and ry = 150 / coords.h ? The answer is, I give preview size 150x150px in CSS.</p>

<pre><code>jcrop_api = undefined
  boundx = undefined
  boundy = undefined

  showUserImageEdit = -&gt;
    if jcrop_api
      jcrop_api.setImage($('#preview').attr('src'));

    update_crop = (coords) -&gt;
      rx = 150 / coords.w
      ry = 150 / coords.h
      $('#preview').css
        width: Math.round(rx * boundx) + 'px'
        height: Math.round(ry * boundy) + 'px'
        marginLeft: '-' + Math.round(rx * coords.x) + 'px'
        marginTop: '-' + Math.round(ry * coords.y) + 'px'
      $('#crop_x').val(Math.floor(coords.x));
      $('#crop_y').val(Math.floor(coords.y));
      $('#crop_w').val(Math.floor(coords.w));
      $('#crop_h').val(Math.floor(coords.h));


    $('#preview-edit').Jcrop({
        bgOpacity: 0.4, 
        bgColor: 'black',
        onChange: update_crop,
        onSelect: update_crop,
        allowSelect: true,
        allowResize: true,
        setSelect: [50, 0, 150, 150],
        aspectRatio: 1
      }, -&gt; 
        bounds = this.getBounds();
        boundx = bounds[0];
        boundy = bounds[1];
        jcrop_api = this);
    $('.uploaded').show()
    $('.fake-button-edit-ok').click -&gt; 
      myModal().setTarget($('.uploaded')).close()
      jcrop_api.release()

  initPreviewImageEdit = -&gt;
    $('.fake-button-edit').click -&gt; 
      showUserImageEdit();
      myModal().setTarget($('.uploaded')).open()
  initPreviewImageEdit();
</code></pre>

<p>whole code <strong>app/assets/javascripts/users/registrations.js.coffee</strong></p>

<pre><code>#= require jquery.Jcrop
jQuery -&gt;
  $(document).on 'ready page:load', -&gt; 

    $('.personal-image-upload').click -&gt; 
      $('.personal-image-upload-file').trigger('click')

    $('.fake-button-upload').click -&gt;
      $('.personal-image-upload-file').trigger('click')

    initPreviewImage = -&gt;    
      readURL = (input) -&gt;
        if input.files and input.files[0]
          reader = new FileReader

          reader.onload = (e) -&gt;
            img = new Image
            img.onload = -&gt;
              $('#crop_x').val(0);
              $('#crop_y').val(0);
              $('#crop_w').val(img.width);
              $('#crop_h').val(img.height);
            img.src = e.target.result

            $('.preview-outer').css("display": "inline-block")
            $('#preview').attr 'src', e.target.result
            $('#preview-edit').attr 'src', e.target.result
            $('.personal-image-upload').hide()
            $('.fake-button-edit').css("display": "block")
            $('.fake-button-edit').css("right": "0px")
            $('.fake-button-upload').css("left": "0px")

          reader.readAsDataURL input.files[0]

      $('#user_main_image_attributes_file').change -&gt;
        readURL this
    initPreviewImage();

    jcrop_api = undefined
    boundx = undefined
    boundy = undefined

    showUserImageEdit = -&gt;
      if jcrop_api
        jcrop_api.setImage($('#preview').attr('src'));

      update_crop = (coords) -&gt;
        rx = 150 / coords.w
        ry = 150 / coords.h
        $('#preview').css
          width: Math.round(rx * boundx) + 'px'
          height: Math.round(ry * boundy) + 'px'
          marginLeft: '-' + Math.round(rx * coords.x) + 'px'
          marginTop: '-' + Math.round(ry * coords.y) + 'px'

        $('#crop_x').val(Math.floor(coords.x));
        $('#crop_y').val(Math.floor(coords.y));
        $('#crop_w').val(Math.floor(coords.w));
        $('#crop_h').val(Math.floor(coords.h));


      $('#preview-edit').Jcrop({
          bgOpacity: 0.4, 
          bgColor: 'black',
          onChange: update_crop,
          onSelect: update_crop,
          allowSelect: true,
          allowResize: true,
          setSelect: [50, 0, 150, 150],
          aspectRatio: 1
        }, -&gt; 
          bounds = this.getBounds();
          boundx = bounds[0];
          boundy = bounds[1];
          jcrop_api = this);

      $('.uploaded').show();

      $('.fake-button-edit-ok').click -&gt; 
        myModal().setTarget($('.uploaded')).close()
        jcrop_api.release()

    initPreviewImageEdit = -&gt;
      $('.fake-button-edit').click -&gt; 
        showUserImageEdit();
        myModal().setTarget($('.uploaded')).open()
    initPreviewImageEdit();
</code></pre>

<h2>6. Carrierwave uploader</h2>

<p>As you can see I create a new version named _customize and it only do its job when model(UserImage model) has crop_x, crop_y, crop_w and crop_h attributes. And I use minimagick crop method to crop image before upload.</p>

<pre><code>class UserImagesUploader &lt; CarrierWave::Uploader::Base

  include CarrierWave::MiniMagick

  storage :fog

  ...

  version :_customize, :if =&gt; :customize? do 
    process :crop_img
    resize_to_fill(150, 150)
  end

  def customize? picture
    !model.crop_x.blank? &amp;&amp; !model.crop_y.blank? &amp;&amp; !model.crop_w.blank? &amp;&amp; !model.crop_h.blank?
  end

  def crop_img
    if !model.crop_x.blank? &amp;&amp; !model.crop_y.blank? &amp;&amp; !model.crop_w.blank? &amp;&amp; !model.crop_h.blank?
      manipulate! do |img|
        x = model.crop_x.to_i
        y = model.crop_y.to_i
        w = model.crop_w.to_i
        h = model.crop_h.to_i
        img.crop("#{w}x#{h}+#{x}+#{y}")
        img
      end
    end
  end

  ...

end
</code></pre>

<h2>7. Final step: Upload Image</h2>

<p>Do you remember that I skip upload callback in <strong>app/models/user_image.rb</strong> ?</p>

<pre><code>skip_callback :save, :after, :store_file!  
</code></pre>

<p>So, when should we do upload job?</p>

<p>Answer is after user save !</p>

<pre><code>class Users::RegistrationsController &lt; Devise::RegistrationsController
  after_filter :upload_avatar, :only =&gt; :create

  ... 

  protected

  def upload_avatar
    if resource.persisted? &amp;&amp; resource.main_image # user is created successfuly
      resource.main_image.store_file! 
      resource.main_image.file.recreate_versions!
    end
  end

  ... 
end
</code></pre>

<h2>8. Don&#8217;t forgot &#8230;</h2>

<p>Since we use Devise for sign up, and we use nested attributes, we need to tell Devise permitted parameters.</p>

<p><strong>app/controllers/applicatio.rb</strong></p>

<pre><code>class ApplicationController &lt; ActionController::Base
  ...
  before_action :configure_permitted_parameters, if: :devise_controller?

  private

  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :password_confirmation, main_image_attributes: ['id', '_destroy', 'file', 'crop_x', 'crop_y', 'crop_w', 'crop_h'])}
  end 
  ... 
end
</code></pre>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[String handling between JavaScript and Ruby]]></title>
    <link href="http://ccaloha.cc/blog/2015/06/10/string-handling-between-javacript-and-ruby/"/>
    <updated>2015-06-10T00:14:49+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/06/10/string-handling-between-javacript-and-ruby</id>
    <content type="html"><![CDATA[<p>javascript 在做 <strong>escape</strong> 時，編碼出來的字，和Ruby的 <strong>CGI.escape</strong>、<strong>URI.escape</strong> 是不同的！</p>

<p>舉例來說：</p>

<p>在<strong>browser console下</strong></p>

<pre><code>escape("台中市")
&gt; "%u53F0%u4E2D%u5E02"
</code></pre>

<p>但是在<strong>rails c</strong> 下</p>

<pre><code>CGI.escape("台中市")
&gt; "%E5%8F%B0%E4%B8%AD%E5%B8%82"
</code></pre>

<p> Why?</p>

<!-- more -->


<p> 查了一下Google，發現是因為在 js 在escape中文時，是將他編成 unicode</p>

<p> 所以，</p>

<h2>javascript 如果要送中文字給Ruby 請使用</h2>

<pre><code>encodeURIComponent("台中市")
&gt; "%E5%8F%B0%E4%B8%AD%E5%B8%82"
</code></pre>

<h2>但是，往往沒辦法你不能去改人家的javascript，你只好&#8230;. javascripts escape string to UTF-8</h2>

<p>查到的作法如下：</p>

<p>在<strong>rails c</strong> 下</p>

<pre><code>unicode_str = "%u53F0%u4E2D%u5E02"

unicode_str.gsub(/\%u([\da-fA-F]{4})/) {|m|  [$1].pack('H*').unpack('n*').pack('U*')}

=&gt; "台中市"
</code></pre>

<p> 參數解釋：</p>

<p> 因為 unicode 的字串都是由 %u 開頭，外加上 4個可能是數字可能是 小寫a~f 或是 大寫A~F，所以我們先透過 <strong>gsub</strong> 這個function將符合的字抓出來處理</p>

<p> 抓出來字之後，我們要使用 <strong>pack</strong> 和 <strong>unpack</strong> 方法，來將字進行解碼、編碼</p>

<p> 我們先抓一個字來看，就以 &#8220;台&#8221; 為例， (ps. <strong>pack 只能用在array, unpack可以用在string</strong>)</p>

<p> gsub 會將 &#8220;53F0&#8221; 丟進去處理</p>

<pre><code>["53F0"].pack('H*')

  =&gt; "S\xF0"
</code></pre>

<p>  # H: 代表了將他pack組成16進位字(hex string (high nibble first))</p>

<pre><code>"S\xF0".unpack('n*')

=&gt; [21488]
</code></pre>

<p>  # n: 他會return一個 Integer，他代表了16-bit unsigned, network (big-endian) byte order</p>

<pre><code>[21488].pack('U*')

=&gt; "台"
</code></pre>

<p>  # U: 將16位元NBO組成UTF-8</p>

<p> REF: <br/>
 <a href="http://www.cnphp6.com/archives/4967">UNESCAPE編碼錯誤</a></p>

<h2>那反過來呢，要如何將UTF-8的字，做成像 javascript escape 後的結果</h2>

<p>在<strong>rails c</strong>下</p>

<pre><code>return_str = ""
"台中市".each_char { |c| return_str += "%u#{c.unpack("U*").pack("n*").unpack("H*").first}" }


return_str 
&gt; "%u53f0%u4e2d%u5e02"
</code></pre>

<h2>假設，你現在要串接的使用Big5寫的API，你發現他接收的parameters 居然是%A5%78%A4%A4%A5%AB</h2>

<p>因為他default是接受，用 javascript escape Big5編碼的字</p>

<p>所以你要將你UTF-8的字，轉成符合他格式的字</p>

<p>這時候你要這樣做：</p>

<pre><code>  return_str = ""
  Iconv.conv("BIG5", "UTF8", str).unpack("H*").first.scan(/../).each do |s|
    return_str += "%#{s}"
  end
</code></pre>

<p>一樣來看說明：</p>

<pre><code>Iconv.conv("BIG5", "UTF8", "台中市")

=&gt; "\x{A578}\x{A4A4}\x{A5AB}"


Iconv.conv("BIG5", "UTF8", "台中市").unpack("H*")

=&gt; ["a578a4a4a5ab"]

Iconv.conv("BIG5", "UTF8", "台中市").unpack("H*").first.scan(/../)

=&gt; ["a5", "78", "a4", "a4", "a5", "ab"]
</code></pre>

<h2>打完收工！</h2>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[機器監控不求人 - 猴子都會用的 Monit(Monitoring your ubuntu machine for dummies)]]></title>
    <link href="http://ccaloha.cc/blog/2015/05/18/monitoring-your-ubuntu-machine-for-dummies/"/>
    <updated>2015-05-18T18:21:20+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/05/18/monitoring-your-ubuntu-machine-for-dummies</id>
    <content type="html"><![CDATA[<p>你是個前端工程師，但是，老闆賦予你<strong>MIS</strong>的神聖任務</p>

<p>還是，你是個後端工程師，無時無刻，要去看一下<strong>mysql</strong>是否還活著，<strong>apache2</strong>是否快掛了</p>

<p>還有，你是個<strong>全端工程師</strong>，雖然你已經強的跟神一樣，但是你就是不想要寫的script自動去重啟的service</p>

<p>這時候，你需要的是<strong><a href="https://mmonit.com/monit/">Monit</a></strong></p>

<p>他可以幫你監控你的daemon processes、檔案系統、Network、Space</p>

<p>如果想要看完整的設定文件請點 <strong><a href="https://mmonit.com/monit/documentation/monit.html">Monit Documentation</a></strong></p>

<p>整理一下，我的使用的感覺</p>

<ol>
<li>使用DSL，所以用起來很直覺</li>
<li>使用 # 當作註解</li>
<li><a href="https://mmonit.com/monit/documentation/monit.html#ACTION">不同的監控，可以有不同的動作(ex: alert, restart, stop, UNMONITOR)</a></li>
<li><a href="https://mmonit.com/monit/documentation/monit.html#SERVICE-POLL-TIME">彈性設定時間來監控</a></li>
<li><a href="https://mmonit.com/monit/documentation/monit.html#SERVICE-RESTART-LIMIT">彈性設定幾次重啟後，就不要再重啟了</a></li>
<li><a href="https://mmonit.com/monit/documentation/monit.html#GENERAL-SYNTAX">可以用if, else &#8230;等來做動作判斷！</a></li>
<li>提供網頁GUI，讓你很好的操作或是看伺服器狀態！</li>
<li>簡單來說，超多客製化應用！網路上也滿多資源可以參考！</li>
</ol>


<p>但是，如果你跟我一樣是個懶人，這時候，你就需要看這篇文章</p>

<!-- more -->


<p>ps. 這是我剛設定好的樣子，等我有發現哪裡有問題，會再上來更新XD</p>

<h2>Step 1. 安裝Monit</h2>

<pre><code>sudo apt-get install monit
</code></pre>

<h2>Step 2. 設定他</h2>

<p>ps. 我一開始看文件，傻傻的以為設定在 <em>~/.monitrc</em>，一切都會work，但是&#8230;經過我約莫半小時的測試&#8230;我放棄了～ 我還是把它寫在</p>

<pre><code>sudo vim /etc/monit/monitrc
</code></pre>

<p>我改了哪些東西</p>

<pre><code># 設定Monit多久監控一次
set daemon 60

#設定log放在哪裡
set logfile /var/log/monit.log

#設定 mail server 
set mailserver 你的.postfix.伺服器 port 25

# ps. 如果你的mail server是在其他地方，譬如AWS SES
# set mailserver email-smtp.us-east-1.amazonaws.com port 587
#   username "amazon_username" password "amazon_password"
#   using TLSV1
#   with timeout 30 seconds

# Email格式
set mail-format {
      from: monit@alohacc.cc
  reply-to: y.alohac@gmail.com
      subject: [Aloha系統通知] $SERVICE $EVENT at $DATE
      message: Monit $ACTION $SERVICE at $DATE on $HOST: $DESCRIPTION.
         Hi, system now is waiting your attention~~~ come on baby~~
         By Aloooooooooooooooooooooha
    }

    #設定收到所有異常通知的人，貌似目前不支援一次設定很多....所以要key很多遍
set alert aloha.chen@itrue.com.tw
set alert y.alohac@gmail.com

# 監控主機CPU、Memory、Swap以及Uptime的數值
check system ccaloha.cc
    if loadavg (1min) &gt; 4 then alert
    if loadavg (5min) &gt; 2 then alert
    if memory usage &gt; 75% then alert
    if swap usage &gt; 25% then alert
    if cpu usage (user) &gt; 70% then alert
    if cpu usage (system) &gt; 30% then alert
    if cpu usage (wait) &gt; 20% then alert


    # 起Web服務
 set httpd port 2812 and
    use address 0.0.0.0      # 我要讓他可以對外連線
    allow localhost           # 開放localhost可以連
    #allow 0.0.0.0/0.0.0.0   # 開放所有IP都可以連得到
    allow 你的.ip.位置        # 只開放你的ip可以連  
    allow 可登入帳號:他的密碼 

    # 監控Apache2
check process apache with pidfile /var/run/apache2/apache2.pid
    start program = "/etc/init.d/apache2 start" with timeout 60 seconds
    stop program  = "/etc/init.d/apache2 stop"
    if cpu &gt; 50% for 2 cycles then alert
    #if TOTAL CPU is greater than 80% for 5 cycles then restart
    if mem &gt; 100 MB for 5 cycles then stop # 網路上參考下來的寫法，但是，monit -t 會報錯
    if failed port 80 for 2 cycles then restart
    #if failed port 443 for 2 cycles with timeout 15 seconds then restart # 網路上參考下來的寫法，但是，monit -t 會報錯
    if failed port 443 for 2 cycles then restart

# 監控mysql
check process mysqld with pidfile /var/run/mysqld/mysqld.pid
    start program = "/etc/init.d/mysql start"
    stop program = "/etc/init.d/mysql stop"
    if failed host 127.0.0.1 port 3306 protocol mysql then alert
    if failed host 127.0.0.1 port 3306 protocol mysql then restart
    if 7 restarts within 10 cycles then unmonitor

# 監控硬碟空間使用
check filesystem root-filesystem with path /dev/xvda1
    if space usage &gt; 80% for 5 times within 15 cycles then alert
</code></pre>

<h2>啟動它</h2>

<pre><code>1. 檢查config 檔是否正確

    sudo monit -t 

2. 啟動

    sudo monit 

3. 如果你改了設定檔，你可以這樣重啟

    sudo monit reload
</code></pre>

<h2>Web Gui</h2>

<p>設定完成後，你就可以看到</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Monitoring%20your%20ubuntu%20machine%20for%20dummies%20-%20Web%20gui.png" alt="Monitoring your ubuntu machine for dummies - Web gui"></p>

<p>點擊一個監控的process，你可以發現，你可以直接透過網頁重啟歐！</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Monitoring%20your%20ubuntu%20machine%20for%20dummies%20-%20Web%20gui%20-%20process%20status.png" alt="Monitoring your ubuntu machine for dummies - Web gui - process status"></p>

<p>另外，你也可以收到email歐</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Monitoring%20your%20ubuntu%20machine%20for%20dummies%20-%20Web%20gui%20-%20Email%20notification.png" alt="Monitoring your ubuntu machine for dummies - Web gui - Email notification"></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[[Ruby on Rails] Send limited mail on staging environment]]></title>
    <link href="http://ccaloha.cc/blog/2015/04/28/send-limited-mail-on-staging-environment/"/>
    <updated>2015-04-28T17:16:33+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/04/28/send-limited-mail-on-staging-environment</id>
    <content type="html"><![CDATA[<p>For simulating production environment, we will build up a stage machine.</p>

<p>Plus, we usually use the copy of production database in stage server.</p>

<p>In some applications, we will send email to users for some purposes such as confirmation mail after user</p>

<p>register, reset password mail&#8230;etc.</p>

<p>And, in staging environment, we have to avoid our operators accidentally send email to real users.</p>

<p>Therefore, we have to implement some codes for filtering.</p>

<p>Let&#8217;s go</p>

<!-- more -->


<p>According to <a href="http://edgeguides.rubyonrails.org/action_mailer_basics.html#intercepting-emails">Action Mailer Basics</a>, we can register an interceptor to avoid some unexpected actions.</p>

<p>And my requirement is I want staging server can send mail to our employees. In our case, our company mail address is <strong>xxx@itrue.com.tw</strong>. So, we have to implement code that only allow email server send to users like: <strong>XXX@itrue.com.tw</strong>.</p>

<h2>Step1. <em>Gemfile</em></h2>

<pre><code>gem 'settingslogic'
</code></pre>

<h2>Step2. Add a lib on <em>app/lib/settings.rb</em></h2>

<pre><code>class Settings &lt; Settingslogic
    source "#{Rails.root}/config/application.yml"
    namespace Rails.env
end
</code></pre>

<h2>Step3. Add your white list on <em>application.yml</em></h2>

<pre><code>defaults: &amp;defaults
  allowed_send_mail_domain: '@itrue.com.tw'

development:
  &lt;&lt;: *defaults

test:
  &lt;&lt;: *defaults

production:
  &lt;&lt;: *defaults
</code></pre>

<h2>Step4. Write a interceptor on <em>app/interceptor/staging_mail_interceptor.rb</em></h2>

<p>ref: <a href="http://renderedtext.com/blog/2012/04/27/filtering-emails-on-staging/">Filtering emails on staging</a></p>

<pre><code>class StagingMailInterceptor

  def self.delivering_email(message)
    message.to = extract_allowed_recepients(message)
    message.perform_deliveries = false if message.to.empty?
  end

  private

  def self.extract_allowed_recepients(message)
    message.to.select { |address| allowed_address?(address) }
  end

  def self.allowed_address?(address)
    allowed_domains = Settings.allowed_send_mail_domain.split(',')

    matches_allowed = allowed_domains.count { |domain| address.include?(domain) }

    matches_allowed != 0
  end

end
</code></pre>

<h2>Step5. Register interceptor to ActionMailer on <em>app/config/initializers/sandbox_email_interceptor.rb</em></h2>

<pre><code>require 'staging_mail_interceptor'

if Rails.env.staging?
  ActionMailer::Base.register_interceptor(StagingMailInterceptor)
end
</code></pre>

<h2>Done</h2>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[[howto] Using Google Analytics Enhanced Ecommerce Tracking your awesome website via Ruby on Rails 如何在Ruby on Rails使用Google Analytics 進階電子商務追蹤]]></title>
    <link href="http://ccaloha.cc/blog/2015/04/13/howto-use-google-analytics-enhanced-ecommerce-tracking-via-ruby-on-rails/"/>
    <updated>2015-04-13T12:15:58+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/04/13/howto-use-google-analytics-enhanced-ecommerce-tracking-via-ruby-on-rails</id>
    <content type="html"><![CDATA[<blockquote><p>i-TRUE 的目標是成為全世界最棒的專業型Database Media Company</p>

<p>我每週一早上負責跟同事們介紹Google Analytics，這是其中一個介紹投影片！</p>

<p>陸續更新中&#8230;</p></blockquote>

<iframe id="iframe_container" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen="" width="550" height="400" src="https://prezi.com/embed/w0azjc2vz3tf/?bgcolor=ffffff&amp;lock_to_path=0&amp;autoplay=0&amp;autohide_ctrls=0#"></iframe>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Integrate Ruby on Rails with Google Analytics API]]></title>
    <link href="http://ccaloha.cc/blog/2015/04/09/integrate-ruby-on-rails-with-google-analytics-api/"/>
    <updated>2015-04-09T22:32:24+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/04/09/integrate-ruby-on-rails-with-google-analytics-api</id>
    <content type="html"><![CDATA[<p>In this article, I try to get some Google Analytics data via Google API. Then, I saved data into MongoDB waiting for further calculation.</p>

<p>And you are able to learn</p>

<ol>
<li>How I do Google API pagination</li>
<li>How I use module to build flexible function</li>
<li>How I use Google Query Explorer to speed up development</li>
<li>How to apply for google API access</li>
</ol>


<p>Let&#8217;s check it out.</p>

<!-- more -->


<h2>Step 1. Apply Google Analytics API access authorization</h2>

<h3>1-1 go to <a href="https://code.google.com/apis/console/">Google API console</a></h3>

<h3>1-2 Find Google Analytics API</h3>

<p> <img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Integrate%20Ruby%20on%20Rails%20with%20Google%20Analytics%20API/find_google_api.png" alt="find google api"></p>

<h3>1-3 Enable Google Analytics API</h3>

<p> <img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Integrate%20Ruby%20on%20Rails%20with%20Google%20Analytics%20API/Enable%20Google%20Analytic%20API.png" alt="enable google analytics API"></p>

<h3>1-4 Create New Client ID</h3>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Integrate%20Ruby%20on%20Rails%20with%20Google%20Analytics%20API/Create%20New%20Client%20ID.png" alt="Create New Client ID"></p>

<h3>1-5 Choose client type</h3>

<p>Since this is for server-to-server usage, I choose this</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Integrate%20Ruby%20on%20Rails%20with%20Google%20Analytics%20API/Choose%20client%20type.png" alt="Choose client type"></p>

<h3>1-6 Download key</h3>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Integrate%20Ruby%20on%20Rails%20with%20Google%20Analytics%20API/download%20key.png" alt="download key"></p>

<p>ps. Since I use Ruby on Rails, so I put it in <strong>config/ga_api_key_20150408.p12</strong></p>

<h3>1-7 Add API user into your GA user group</h3>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Integrate%20Ruby%20on%20Rails%20with%20Google%20Analytics%20API/Add%20API%20user%20into%20your%20GA%20user%20group.png"></p>

<h2>2. [TIPS] Use Google Query Explorer to speed up development</h2>

<h3>1. Go to <a href="https://ga-dev-tools.appspot.com/query-explorer/">Google Query Explorer</a></h3>

<h3>2. Get your view id</h3>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Integrate%20Ruby%20on%20Rails%20with%20Google%20Analytics%20API/Get%20your%20view%20id.png"></p>

<h3>3. Try your query parameter first and know how your data look like</h3>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Integrate%20Ruby%20on%20Rails%20with%20Google%20Analytics%20API/Try%20your%20query%20parameter%20first.png"></p>

<p>And it provides a easy tool that you don&#8217;t need to remember any parameters.</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/Integrate%20Ruby%20on%20Rails%20with%20Google%20Analytics%20API/parameter%20tool.png"></p>

<h2>3. Let&#8217;s write a sample code</h2>

<h3>3-1 Add gem to Gemfile</h3>

<pre><code>gem 'google-api-client'
</code></pre>

<h3>3-2 create a rb file</h3>

<p>I update some codes copied from this page.<a href="https://gist.github.com/joost/5344705">https://gist.github.com/joost/5344705</a></p>

<pre><code>  require 'google/api_client'
  require 'date'

  client = Google::APIClient.new(:application_name =&gt; 'Urcosme-GA-sync', :application_version =&gt; '1')

  key_file = File.join("#{Rails.root}/config", 'ga_api_key_20150408.p12')
  key = Google::APIClient::PKCS12.load_key(key_file, 'notasecret')
  service_account = Google::APIClient::JWTAsserter.new(
      'hello@developer.gserviceaccount.com',
      ['https://www.googleapis.com/auth/analytics.readonly', 'https://www.googleapis.com/auth/prediction'],
      key)
  client.authorization = service_account.authorize

  analytics = client.discovered_api('analytics', 'v3')

  parameters = {
        'ids'         =&gt; "ga:12345",
        'start-date'  =&gt; (Date.today - 30).strftime("%Y-%m-%d"),
        'end-date'    =&gt; Time.now.strftime("%Y-%m-%d"),
        'metrics'     =&gt; "ga:avgTimeOnPage",
        'filters'     =&gt; "ga:pagePath=~/"
      }
  result = client.execute(:api_method =&gt; analytics.data.ga.get, :parameters =&gt; parameters)
</code></pre>

<p>This sample code demonstrates how <strong>google-api-client</strong> work.
However, I need to fetch different kinds of data such as specific pageviews, event, and campaign. I have to refactor it.</p>

<h2>4. Let&#8217;s build a flexible code</h2>

<h2>4-1 create a module file: &#8220;ga.rb&#8221; in /lib/</h2>

<p>In above code, it can be seperated to 2 parts. One is uniform part, the other is variable part.</p>

<p>Unchange part</p>

<pre><code>@client = Google::APIClient.new(:application_name =&gt; 'Urcosme-GA-sync', :application_version =&gt; '1')
key_file = File.join("#{Rails.root}/config", 'ga_api_key_20150408.p12')
key = Google::APIClient::PKCS12.load_key(key_file, 'notasecret')
service_account = Google::APIClient::JWTAsserter.new(
          'hello@developer.gserviceaccount.com',
          ['https://www.googleapis.com/auth/analytics.readonly', 'https://www.googleapis.com/auth/prediction'],
          key)
@client.authorization = service_account.authorize
@analytics = @client.discovered_api('analytics', 'v3')
</code></pre>

<p>Change part. In this code, I already write some default parameters.
But, it can be rewrite in other place.</p>

<pre><code>@data_date = Date.today - 1
@parameters = {
    'ids'         =&gt; "ga:12345",
    'start-date'  =&gt; @data_date.strftime("%Y-%m-%d"),
    'end-date'    =&gt; @data_date.strftime("%Y-%m-%d"),
    'metrics'     =&gt; "",
    'dimensions'  =&gt; "",
    'max-results' =&gt; "10000"
}
</code></pre>

<p>And the whole code show as below.</p>

<p><strong>lib/ga.rb</strong></p>

<pre><code>require 'google/api_client'
require 'date'
module GoogleAnalytic

  # 每一個GA API執行前，需要初始化
  def initialize_ga
    @client = Google::APIClient.new(:application_name =&gt; 'Urcosme-GA-sync', :application_version =&gt; '1')
    key_file = File.join("#{Rails.root}/config", 'ga_api_key_20150408.p12')
    key = Google::APIClient::PKCS12.load_key(key_file, 'notasecret')
    service_account = Google::APIClient::JWTAsserter.new(
      'hello@developer.gserviceaccount.com',
      ['https://www.googleapis.com/auth/analytics.readonly', 'https://www.googleapis.com/auth/prediction'],
      key)
    @client.authorization = service_account.authorize
    @analytics = @client.discovered_api('analytics', 'v3')
    @data_date = Date.today - 1
    @parameters = {
        'ids'         =&gt; "ga:12345",
        'start-date'  =&gt; @data_date.strftime("%Y-%m-%d"),
        'end-date'    =&gt; @data_date.strftime("%Y-%m-%d"),
        'metrics'     =&gt; "",
        'dimensions'  =&gt; "",
        'max-results' =&gt; "10000"
      }
  end

  # 將資料儲存到MongoDB，請複寫我
  def sync_to_db(result)
  end
end
</code></pre>

<h2>4-2 let&#8217;s Rails application know our lib</h2>

<p><strong>config/application.rb</strong></p>

<pre><code>module MyAwesomeApp
  class Application &lt; Rails::Application
    config.autoload_paths += %W(#{config.root}/lib)   end
end
</code></pre>

<h2>4-3 create a model to save API data</h2>

<p>Now I want to get all our campaign data. So I create &#8230;</p>

<p>ps. I use MongoDB as database.</p>

<p><strong>app/models/ga_campaign.rb</strong></p>

<pre><code>class GACampaign
  include GoogleAnalytic
  include Mongoid::Document
  include Mongoid::Timestamps
  field :campaign_name, type: String      # 廣告活動名稱
  field :campaign_source, type: String    # 廣告活動來源    
  field :campaign_medium, type: String    # 廣告活動媒介
  field :campaign_content, type: String   # 廣告活動內容
  field :session, type: Integer
  field :pageview, type: Integer
  field :data_date, type: Date            # 原始資料的時間

  def get_campaign_from_ga_api
    initialize_ga
    @parameters['metrics'] = "ga:pageviews,ga:sessions"
    @parameters['dimensions'] = "ga:campaign,ga:source,ga:medium,ga:adContent"
    @parameters['filters'] = "ga:campaign!=(not set)"
    sync_to_db(@parameters)
  end

  def sync_to_db(parameters)
     request = {
        :api_method =&gt; @analytics.data.ga.get,
        :parameters =&gt; parameters
     }
    result = @client.execute(request)
    result.data.rows.each do |array_data|
        GACampaign.create(campaign_name:    array_data[0], 
                          campaign_source:  array_data[1], 
                          campaign_medium:  array_data[2],
                          campaign_content: array_data[3],
                          session:          array_data[4],
                          pageview:         array_data[5],
                          data_date:        @data_date)
    end
  end
end
</code></pre>

<p>But&#8230;. if result is more than max-result (according to google, maximun is 10000 <a href="https://developers.google.com/analytics/devguides/reporting/core/v3/reference#maxResults">see google doc</a>)</p>

<p>How can I do paginate?</p>

<h2>4-4 Google API pagination</h2>

<p>Since, this method will be used in every &#8220;GA model&#8221; so I put it in <strong>lib/ga.rb</strong></p>

<p>according to GA document</p>

<blockquote><p>If not supplied, the starting index is 1. (Result indexes are 1-based. That is, the first row is row 1, not row 0.) Use this parameter as a pagination mechanism along with the max-results parameter for situations when totalResults exceeds 10,000 and you want to retrieve rows indexed at 10,001 and beyond.</p></blockquote>

<p>here is my method. Not quiet good, but it&#8217;s working.</p>

<pre><code>def query_paginate(parameters)
     request = {
      :api_method =&gt; @analytics.data.ga.get,
      :parameters =&gt; parameters
    }
    count = 0
    loop do
      result = @client.execute(request)
      sync_to_db(result)
      max_count = result.data.total_results / parameters["max-results"].to_i
      break if count == max_count
      count += 1
      request[:parameters]["start-index"] = parameters["max-results"].to_i * count + 1
    end
  end
</code></pre>

<p>let&#8217;s see full code.</p>

<pre><code>require 'google/api_client'
require 'date'
module GoogleAnalytic

def initialize_ga
    @client = Google::APIClient.new(:application_name =&gt; 'Urcosme-GA-sync', :application_version =&gt; '1')
    key_file = File.join("#{Rails.root}/config", 'ga_api_key_20150408.p12')
    key = Google::APIClient::PKCS12.load_key(key_file, 'notasecret')
    service_account = Google::APIClient::JWTAsserter.new(
      'hello@developer.gserviceaccount.com',
      ['https://www.googleapis.com/auth/analytics.readonly', 'https://www.googleapis.com/auth/prediction'],
      key)
    @client.authorization = service_account.authorize
    @analytics = @client.discovered_api('analytics', 'v3')
    @data_date = Date.today - 1
    @parameters = {
        'ids'         =&gt; "ga:12345",
        'start-date'  =&gt; @data_date.strftime("%Y-%m-%d"),
        'end-date'    =&gt; @data_date.strftime("%Y-%m-%d"),
        'metrics'     =&gt; "",
        'dimensions'  =&gt; "",
        'max-results' =&gt; "10000"
      }
  end

  # 將資料儲存到MongoDB，請複寫我
  def sync_to_db(result)
  end

  # 如果API存取，外加換頁功能，因為GA一次request最多一萬筆資料
  def query_paginate(parameters)
     request = {
      :api_method =&gt; @analytics.data.ga.get,
      :parameters =&gt; parameters
    }
    count = 0
    loop do
      result = @client.execute(request)
      sync_to_db(result)
      max_count = result.data.total_results / parameters["max-results"].to_i
      break if count == max_count
      count += 1
      request[:parameters]["start-index"] = parameters["max-results"].to_i * count + 1
    end
  end
end
</code></pre>

<h3>4-5 refactor <strong>app/models/ga_campaign.rb</strong></h3>

<pre><code>class GACampaign
  include GoogleAnalytic
  include Mongoid::Document
  include Mongoid::Timestamps
  field :campaign_name, type: String      # 廣告活動名稱
  field :campaign_source, type: String    # 廣告活動來源
  field :campaign_medium, type: String    # 廣告活動媒介
  field :campaign_content, type: String   # 廣告活動內容
  field :session, type: Integer
  field :pageview, type: Integer
  field :data_date, type: Date            # 原始資料的時間

  def get_campaign_from_ga_api
    initialize_ga
    @parameters['metrics'] = "ga:pageviews,ga:sessions"
    @parameters['dimensions'] = "ga:campaign,ga:source,ga:medium,ga:adContent"
    @parameters['filters'] = "ga:campaign!=(not set)"
    query_paginate(@parameters)
  end

  def sync_to_db(result)
        result.data.rows.each do |array_data|
        GACampaign.create(campaign_name:    array_data[0], 
                          campaign_source:  array_data[1], 
                          campaign_medium:  array_data[2],
                          campaign_content: array_data[3],
                          session:          array_data[4],
                          pageview:         array_data[5],
                          data_date:        @data_date)
    end
  end
end
</code></pre>

<h3>4-6 how to use</h3>

<pre><code>ga_campaign = GACampaign.new
ga_campaign.get_campaign_from_ga_api
</code></pre>

<h3>4-7 other example: fetch GA Event data</h3>

<p>The things I have to change are <strong>@parameters</strong> and <strong>sync_to_db</strong></p>

<p>It&#8217;s easy, right~</p>

<p><strong>app/models/ga_event.rb</strong></p>

<pre><code>class GAEvent
  include GoogleAnalytic
  include Mongoid::Document
  include Mongoid::Timestamps
  field :event_category, type: String      # 事件類別
  field :event_label, type: String         # 事件標籤
  field :event_action, type: String        # 事件動作
  field :total_events, type: Integer       
  field :unique_events, type: Integer
  field :data_date, type: Date            # 原始資料的時間

  def get_event_from_ga_api
    initialize_ga
    @parameters['metrics'] = "ga:totalEvents,ga:uniqueEvents"
    @parameters['dimensions'] = "ga:eventCategory,ga:eventLabel,ga:eventAction"
    @parameters['filters'] = "ga:eventCategory==test"
    query_paginate(@parameters)
  end

  def sync_to_db(result)
    result.data.rows.each do |array_data|
      GAEvent.create(event_category:      array_data[0], 
                     event_label:        array_data[1], 
                     event_action:       array_data[2],
                     total_events:       array_data[3],
                     unique_events:      array_data[4],
                     data_date:        @data_date)

    end
  end

end
</code></pre>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Using Google Analytics for Email Click Tracking And Open Rate Tracking in Ruby on Rails]]></title>
    <link href="http://ccaloha.cc/blog/2015/02/27/using-google-analytics-for-email-click-tracking-and-open-rate-tracking-in-ruby-on-rails/"/>
    <updated>2015-02-27T23:34:39+08:00</updated>
    <id>http://ccaloha.cc/blog/2015/02/27/using-google-analytics-for-email-click-tracking-and-open-rate-tracking-in-ruby-on-rails</id>
    <content type="html"><![CDATA[<h2>Requirement</h2>

<ol>
<li><p>每個連結的點擊數</p></li>
<li><p>開信率</p></li>
</ol>


<h2>Google Analytics</h2>

<p>這是一個電子報，基本上屬於一種廣告！</p>

<p>那GA怎麼那麼厲害知道誰點了什麼？</p>

<p>該不會Google 大神，連Email也滲透？！</p>

<p>ps. 我猜他應該也差不多都知道我們在Email上的一舉一動，只要你用Gmail&#8230;</p>

<h3>那GA要怎麼紀錄？</h3>

<blockquote><p>基本上就是將<strong>連結網址</strong>帶上一些&#8221;參數&#8221;，當使用者點了這個連結之後，就你就會被帶到那個<strong>連結網址</strong>，這時候那個網站一定有埋GA的javascript
，這就是他的Key~ GA透過js爬到你的網址，當他發現你有帶某些參數，他就知道你是從哪裡來的！</p></blockquote>

<!-- more -->


<p>我剛剛提到了好幾次<strong>參數</strong></p>

<p>沒錯！這就是關鍵！</p>

<p>讓我們來看一下 有哪些參數</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/utm%E8%A8%AD%E8%A8%88.png"></p>

<p>GA在有文件說明<a href="https://support.google.com/analytics/answer/1033863">網址要如何使用</a></p>

<p>而且，GA還很貼心的幫大家準備了 <a href="https://support.google.com/analytics/answer/1033867?hl=zh-Hant">網址產生器</a></p>

<p>來看一下，實際上會產生什麼樣的連結：</p>

<p>假設我們追蹤</p>

<pre><code>透過Email點擊，來到首頁的人
</code></pre>

<p>Url 原本是長這樣</p>

<pre><code>www.urcosme.com
</code></pre>

<p>ps. urcosme.com小弟目前待的公司 XDDDDD廣告一下！</p>

<p>經過<del>我的</del>GA的巧手，它會長這樣</p>

<pre><code>www.urcosme.com/?utm_source=%E9%9B%BB%E5%AD%90%E5%A0%B1&amp;utm_medium=%E9%9B%BB%E5%AD%90%E9%83%B5%E4%BB%B6&amp;utm_content=%E6%B8%AC%E8%A9%A6%E6%B8%AC%E8%A9%A6&amp;utm_campaign=%E6%88%91%E5%A5%BD%E5%B8%A5
</code></pre>

<p>案&#8230;.這是&#8230;.!？</p>

<p>讓我幫你翻譯一下</p>

<pre><code>www.urcosme.com/?utm_source=電子報&amp;utm_medium=電子郵件&amp;utm_content=測試測試&amp;utm_campaign=我好帥
</code></pre>

<p>當我把連結改成這樣後，基本上使用者也是會點到你的首頁，但是對於GA而言，他就知道你是從電子郵件過來</p>

<p>但! 身為一個有責任感又假掰的IT，</p>

<p>我們當然希望讓系統自動幫行銷的同仁帶入這些參數，讓正妹同事覺得沒有你不行～</p>

<h2>1. Rails 要如何實作“點擊追蹤”</h2>

<p>其實很簡單，我們要寫一個 小小小爬蟲，把內容爬過一次</p>

<p>把有 &lt;a> 的找出來，然後把資訊加進去就好了！</p>

<p>那&#8230;爬蟲要怎麼寫？</p>

<p>各位，既然是來到 Rails ，Rails什麼不多，鐵路最多，歐不～ 是輪子最多！</p>

<p>跟大家介紹個 Gem : <strong><a href="http://www.nokogiri.org/">Nokogiri</a></strong></p>

<p>我們就直接用實作，來說明他可以幹麻</p>

<p>直接來看的程式碼：</p>

<p>這是我寫在 <strong>app/models/edm.rb</strong> 的code</p>

<p>這個model資訊是這樣</p>

<pre><code># == Schema Information
#
# Table name: edms
#
#  id                 :integer          not null, primary key
#  name               :string
#  send_at            :datetime
#  title              :string(255)
#  state              :string(255)
#  content            :text
#  created_at         :datetime
#  updated_at         :datetime
#


def parse_link_in_email(user_id)
    # 讀進email html
    html =  Nokogiri::HTML(self.content)
    # 找出所有 a 
    a_nodes = html.css('a')
    a_nodes.each do |a|
      # 抓出href 並且加上GA 追蹤
      tracking_a = a['href'] +  "?utm_source=#{self.name}-#{CGI.escape(a['href'])}" + 
                                "&amp;utm_medium=email" +
                                "&amp;utm_content=#{self.id}-#{user_id}" + 
                                "&amp;utm_campaign=#{self.title}"
      a['href'] = tracking_a
    end
    return html.to_html
 end
</code></pre>

<p>幾個重點：</p>

<ol>
<li>使用<strong>Nokogiri::HTML(self.content)</strong>  => 讀HTML進來</li>
<li><strong>html.css(&#8216;a&#8217;)</strong> => 抓出所有&lt;a></li>
<li><strong>a[&#8216;href&#8217;]</strong>     => 抓出這個&lt;a>，裡頭的屬性href</li>
<li><strong>html.to_html</strong>  => 轉回HTML</li>
</ol>


<p>Done !</p>

<p>所以當使用者點擊信件的link時，GA就會看到&#8230;..</p>

<p><strong>攬客 >> 廣告活動 >> 所有廣告活動</strong></p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/Blog%20Image/GA_email_click_tracking.png" alt="GA email click tracking demo"></p>

<h2>2. Rails 要如何實作“開信率”</h2>

<p>開信率我是參考這篇<a href="http://dyn.com/blog/tracking-email-opens-via-google-analytics/">Blog</a>來實作</p>

<p>簡單來說，我們需要埋一個image tag，然而那個tag</p>

<p>會帶上一些<strong>參數</strong>，讓GA知道這幹嘛的！</p>

<p>為什麼要使用這種標籤的方式紀錄呢？</p>

<p>假設使用者打開信之後，並且點了一個連結，</p>

<p>因為這個點了連結的動作，我們知道他一定有開信，歐噎～ 做完了～</p>

<p>But&#8230;&#8230;&#8230;..</p>

<p>那如果他只有打開信，沒有點擊勒？ 如果他打開信，點了N個連結，那開信不就也被多紀錄了很多次？(ps.點擊紀錄是每點一次就紀錄一次)</p>

<blockquote><p>所以我們必須透過從 Google Analytics <strong>&#8220;GET&#8221;</strong> 一個標記(就是圖片啦)，當我們跟GA要圖的時候，可以告訴GA一些<strong>參數</strong>，嘿嘿，這樣就有辦法紀錄了！</p></blockquote>

<p>各位，又看到<strong>參數</strong>兩個字</p>

<p>那又有哪些可以用勒？ (這邊我只列我目前有在用的，詳細可以參考<a href="https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#events">GA collections parameters</a>)</p>

<table>
<thead>
<tr>
<th></th>
<th> 參數                                                                                 </th>
<th> 說明                                                                   </th>
<th> 舉例                                                                                 </th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td> tid </td>
<td> 要放GA的ID</td>
<td> UA-1234567-8 |</td>
</tr>
<tr>
<td></td>
<td> uid </td>
<td>Unique的ID(因為我們要寄信給User我是使用該User的ID)</td>
<td> 1 |</td>
</tr>
<tr>
<td></td>
<td> t   </td>
<td> 告訴GA這是個什麼類型的紀錄(基本上我查到都使用event) </td>
<td> event |</td>
</tr>
<tr>
<td></td>
<td> ec  </td>
<td> 告訴GA這個Event的Category </td>
<td> email-測試 |</td>
</tr>
<tr>
<td></td>
<td> ea  </td>
<td> 告訴GA這個Event的Action </td>
<td> Open |</td>
</tr>
<tr>
<td></td>
<td> el  </td>
<td> 告訴GA這個Event的Label </td>
<td> user_id-1 |</td>
</tr>
<tr>
<td></td>
<td> cs  </td>
<td> 廣告活動的來源 </td>
<td> Email標題 |</td>
</tr>
<tr>
<td></td>
<td> cm  </td>
<td> 廣告活動的媒介 </td>
<td> edm |</td>
</tr>
<tr>
<td></td>
<td> cn  </td>
<td> 廣告活動名稱   </td>
<td> 電子報第0期 | </td>
</tr>
</tbody>
</table>


<p>那我們就不看sample，直接來看實作code</p>

<pre><code>def parse_link_in_email(user_id)

    ... 

    body = html.at_css("body")
    img_node = Nokogiri::XML::Node.new("img",body)
    img_node['src'] = "https://www.google-analytics.com/collect?v=1" + 
                      "&amp;tid=#{Settings.google_analytics_key}" + 
                      "&amp;uid=#{user_id}"+
                      "&amp;t=event" +
                      "&amp;ec=email-#{self.name}-#{self.title}" + 
                      "&amp;ea=open" + 
                      "&amp;el=user_id-#{user_id}" + 
                      "&amp;cs=#{self.name}" + 
                      "&amp;cm=email" + 
                      "&amp;cn=#{self.title}"
    body &lt;&lt; img_node

    ...
end
</code></pre>

<p>幾個重點：</p>

<ol>
<li>使用<strong>Nokogiri::XML::Node.new(&#8220;img&#8221;,body)</strong>  => 新增一個&lt;img>標籤</li>
<li><strong>img_node[&#8216;src&#8217;]</strong>     => 設定這個&lt;img>，裡頭的屬性src</li>
<li><strong>body &lt;&lt; img_node</strong>  => 將code埋到body裡面</li>
</ol>


<p>那在GA你會看到什麼勒？</p>

<p><img src="https://dl.dropboxusercontent.com/u/22307926/GA_email_open_tracking.png" alt="GA email open rate tracking demo"></p>

<h2>3. Rails Mailer、its View and model</h2>

<p> 剩下我沒提到的部份，不過這些就是基本寄信的功能，我就不贅述了</p>

<p> <strong>app/models/edm.rb</strong></p>

<pre><code>has_many :edm_user_ships  # 假設你有要寄信的清單
has_many :users, :through =&gt; :edm_user_ships
def send_mail
    self.users.each do |user|
        EdmMailer.delay.send_edm(self, user.email, user.id)
    end
end
</code></pre>

<p><strong>app/mailers/edm_mailer.rb</strong></p>

<pre><code>class EdmMailer &lt; ActionMailer::Base
  default from: "service@urcosme.com"

  def send_edm(e_notify, email, user_id = nil)
    @e_notify = e_notify
    @user_id = user_id

    mail to: email, subject: e_notify.title
  end
end
</code></pre>

<p><strong>app/views/edm_mailer/send_edm.html.slim</strong></p>

<pre><code>= raw @e_notify.parse_link_in_email(@user_id)
</code></pre>

<h2>Done</h2>
]]></content>
  </entry>
  
</feed>
