<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
  <id>tag:blog.hothero.org,2005:/posts</id>
  <link rel="alternate" type="text/html" href="http://blog.hothero.org"/>
  <link rel="self" type="application/atom+xml" href="http://blog.hothero.org/posts.atom"/>
  <title>hothero&amp;#39;s blog</title>
  <updated>2016-06-04T16:27:04+08:00</updated>
  <entry>
    <id>tag:blog.hothero.org,2005:Post/736055</id>
    <published>2016-06-04T16:27:00+08:00</published>
    <updated>2016-06-04T16:45:38+08:00</updated>
    <link rel="alternate" type="text/html" href="http://blog.hothero.org/posts/2016/06/04/reinitialize-the-facebook-social-plugin-infinite-scolling"/>
    <title>reinitialize the facebook social plugin on infinite scolling</title>
    <content type="html">&lt;p&gt;一般 facebook social plugin 初始化方式會是以下的程式碼，藉由引入外部 javascript 檔案作第三方服務需要的事情，以臉書來說就是判別 fb-root，根據裡面的屬性產生像是「讚」的按鈕。觸發時機多是在第一次網頁讀取完成的同時執行。&lt;/p&gt;

&lt;p&gt;而 infinite scrolling 產生更多網站內容的作法流程是，隨著網頁滾動，不斷透過 ajax 方式讀取後台資料呈現出來，導致產生出來的 facebook social plugin 程式碼沒有辦法被初始化。&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&amp;lt;div id="fb-root"&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;script&amp;gt;
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
    js = d.createElement(s); js.id = id;
    js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&amp;amp;appId=679022945540034&amp;amp;version=v2.0";
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'facebook-jssdk'));
&amp;lt;/script&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;解決方法&lt;/h3&gt;
&lt;p&gt;方法很簡單，只要在完成資料呈現的同時，加入以下程式碼即可。&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;FB.XFBML.parse();
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href="https://developers.facebook.com/docs/reference/javascript/FB.XFBML.parse"&gt;官方文件&lt;/a&gt;是這麼描述這行程式碼的用途&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can use this function to re-render social plugins using either the XFBML syntax (e.g. &lt;a href="fb:like" rel="nofollow" target="_blank"&gt;fb:like&lt;/a&gt;) or the HTML5 syntax (e.g. &lt;code&gt;&amp;lt;div class="fb-like"&amp;gt;&lt;/code&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;也就是說只要呼叫這行程式碼，就會將整份網站內，所有根據 XFBML 語法的元素，重新再被初始化呈現一次。FBML 則是 FaceBook Markup Language 的縮寫，XFBML 即是代表 HTML + FBML 的意思。&lt;/p&gt;
&lt;h3&gt;進階用法&lt;/h3&gt;
&lt;p&gt;在 parse 的函式內甚至可以針對新加到網站內容內的元素直接作重新初始化，避免原先已經存在的 social plugin 又再次被初始化。&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;FB.XFBML.parse(document.getElementById('foo'));
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
</content>
    <author>hothero</author>
  </entry>
  <entry>
    <id>tag:blog.hothero.org,2005:Post/732382</id>
    <published>2016-05-13T00:07:00+08:00</published>
    <updated>2016-05-13T00:08:04+08:00</updated>
    <link rel="alternate" type="text/html" href="http://blog.hothero.org/posts/2016/05/13/share-online-english-learning-services"/>
    <title>[分享] 線上英文學習服務</title>
    <content type="html">&lt;p&gt;今天想來分享個好東西，一個線上英文課程網站 Engoo：&lt;a href="https://engoo.com.tw/" rel="nofollow" target="_blank"&gt;https://engoo.com.tw/&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;可能有許多朋友跟我一樣，常找不到時間、覺得上課太貴、甚至沒有方向等等。自從我親愛的好友 @mymy 推薦給我 engoo 後，好像覺得學英文沒那麼困難了呢（是電視廣告嗎...）&lt;/p&gt;

&lt;p&gt;讓我特別心動的一個最主要原因，是每次上課只要「25 分鐘」，然後一堂不到100元。對於一個時間零碎的工作者來說，是一個非常舒服的時間安排。另外在安排課程上也與一般的英文課程不一樣，是每次上完課才安排下一次的課程時間。這部份我覺得有幾個好處：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;每週固定時間容易臨時有事要調課很麻煩&lt;/li&gt;
&lt;li&gt;每次安排下次上課時間，通常是隔天或過一兩天，時間較好掌握&lt;/li&gt;
&lt;li&gt;更重要的是，能不能上到好老師的課真的是各憑本事，因為 Engoo 只能預約「下一堂」，而非一口氣預約。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上課方式主要兩種：聊天、教材，端看自己的需求。因為是用 Skype 上課（申請帳號會填 skype ID，每次上課老師會加 skype 通訊錄），加上開視訊其實就像跟外國人在對話一樣，skype 品質也很穩定。&lt;/p&gt;

&lt;p&gt;教材部分從醫學、旅行、世界文學到多益還有口說測驗其實都有，也有分入門到高級不同的層級可以選擇，算是非常的完整。自己上了半個多月到一個月，一開始主要是上商業教材為主，上到後面有點單調，就換每日新聞的教材惹～&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;最後就是很重要的一環「老師」，在 Engoo 很棒的就是絕大部分的老師都有錄自我介紹影片，從影片中可以看一下這老師順不順眼，聽一下老師的口音順不順耳，偶爾會有那種超級正的老師出現 XDD（真的是在上課不是看老師）。而目前上到的都是菲律賓與賽爾維亞人居多，我猜想是這個價位一般美國或是歐洲人老師是不大會願意加入的。但整體來說水準也都不差，畢竟英文也都是他們母語，口音部分倒覺得還好，看自我介紹影片這足以彌補小小的缺陷。&lt;/p&gt;

&lt;p&gt;有朋友好奇這樣是不是就沒辦法上到一樣的老師，的確是這樣沒錯。但拜 Engoo 預約課程的機制所賜，還是有很大機率可以上到一樣的老師，只要一上完課就馬上預約下次上課時間；換個方向想，其實每次能嘗試不同老師何嘗不是個認識更多外國人，更不懼怕的一個方式呢～但若是要認真準備檢定考試那種需求的朋友，就不大建議這個方式了。&lt;br&gt;
是說前幾天上課的老師，居然是國外媒體的人，也常報群眾集資議題，還說要跟他 BOSS 提一下貝殼放大，也算是意外的收穫 XDDD&lt;/p&gt;

&lt;p&gt;說到這裡，還有另個重要的關鍵要素「價錢」。目前買的方案是一個月 1900（換算下來一堂課只要63.333...），每天只能上一堂課，如果想要每天上兩堂課就要再加錢惹，也就是說不能今天沒上然後補到明天上，沒這回事喔。然後剛申請帳號可以免費上課兩堂，算是試用這樣。&lt;/p&gt;

&lt;p&gt;對惹，5/16就要漲價到每月2360了，想要的朋友可以考慮一下。&lt;br&gt;
對惹，5/16就要漲價到每月2360了，想要的朋友可以考慮一下。&lt;br&gt;
對惹，5/16就要漲價到每月2360了，想要的朋友可以考慮一下。&lt;/p&gt;

&lt;p&gt;收尾就是有去研究一下「How to thinking in english」，有篇文章提到有三：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learn vocabulary and phrases for specific situations&lt;/li&gt;
&lt;li&gt;Learn with native speakers&lt;/li&gt;
&lt;li&gt;Study English in short blocks of time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Engoo 算是完全符合。想要找人一起加入，彼此推薦一下好老師 :D&lt;/p&gt;
</content>
    <author>hothero</author>
  </entry>
  <entry>
    <id>tag:blog.hothero.org,2005:Post/731818</id>
    <published>2016-05-10T23:00:00+08:00</published>
    <updated>2016-05-10T23:07:42+08:00</updated>
    <link rel="alternate" type="text/html" href="http://blog.hothero.org/posts/2016/05/10/git-commit-crash"/>
    <title>git commit crash 小插曲</title>
    <content type="html">&lt;p&gt;今天操作 git 發生一件離奇事件，就是 git commit 竟然也會有消失的一天...&lt;/p&gt;

&lt;p&gt;發生在 git commit 後在 overcommit 這個 gem 在確認 coding style 時，發現 commit message 少打一個字母...情急之下按下 ctrl+c 意圖終止&lt;/p&gt;

&lt;p&gt;&lt;img src="http://user-image.logdown.io/user/1131/blog/1115/post/731818/Bg4aT9BxQ9xsiqHL8QuA_git-commit-crash.jpg" alt="git-commit-crash.jpg"&gt;&lt;/p&gt;

&lt;p&gt;沒想到就此 git commit 消失，特此紀念居然能有此成就達成。&lt;/p&gt;

&lt;p&gt;BTW, 還很懶得研究到底問題出在哪..&lt;/p&gt;
</content>
    <author>hothero</author>
  </entry>
  <entry>
    <id>tag:blog.hothero.org,2005:Post/731312</id>
    <published>2016-05-09T18:41:00+08:00</published>
    <updated>2016-05-09T19:19:10+08:00</updated>
    <link rel="alternate" type="text/html" href="http://blog.hothero.org/posts/2016/05/09/git-file-extension-case-sensitive"/>
    <title>heroku about file extension case sensitive</title>
    <content type="html">&lt;p&gt;許多人對於檔案附檔名甚至是檔名命名方式常有不同習慣，甚至是因為不小心，導致附檔名有大小寫區分。最常見的就是 file1.jpg, file2.JPG, file3.png, file5.PNG。&lt;/p&gt;

&lt;p&gt;最近在使用 heroku 上就遇到有關大小寫的問題，會有幾個狀況發生：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在網址的附檔名上有大小寫區分&lt;/li&gt;
&lt;li&gt;瀏覽器的 cache 也會有&lt;/li&gt;
&lt;li&gt;git 也有此問題&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最基本的作法就是將所有檔名與附檔名都改為小寫，這邊可以使用指令完成&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;rename 'y/A-Z/a-z/' *
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;但若是 MAC 或是 Windows 這種 case-insensitive（大小寫沒差）的系統，需要加上參數 -f&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;rename -f 'y/A-Z/a-z/' *
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;順道一提，基本上 Linux 通常都是 case-sensitive 的系統。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;那在本機（MAC）做完後發現，git 沒有任何動靜，即使改了幾十個 image 的檔名從大寫變成小寫，最後發現問題出在 GIT。&lt;/p&gt;

&lt;p&gt;GIT 在預設狀態是不管大小寫，都視為同樣的檔案，不會有任何變動的反應，需要設定告知目前系統是有大小寫區分的：&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;git config core.ignorecase false
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;設定完後剛所有變動檔名的檔案就是可以進到 stage 的狀態了！&lt;/p&gt;
</content>
    <author>hothero</author>
  </entry>
  <entry>
    <id>tag:blog.hothero.org,2005:Post/707302</id>
    <published>2016-04-10T17:54:00+08:00</published>
    <updated>2016-04-10T18:04:17+08:00</updated>
    <link rel="alternate" type="text/html" href="http://blog.hothero.org/posts/2016/04/10/707302"/>
    <title>Fix ahoy no implicit conversion of Hash into String</title>
    <content type="html">&lt;p&gt;Someone got some trouble "no implicit conversion of Hash into String" after upgraded properties field from json to jsonb type in &lt;a href="https://github.com/ankane/ahoy"&gt;Ahoy&lt;/a&gt;. Or you can follow this &lt;a href="https://github.com/ankane/ahoy/issues/86"&gt;issue#86&lt;/a&gt; or &lt;a href="https://github.com/ankane/ahoy/issues/120"&gt;issue#120&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is a easy way to fix it. Just add following content in ahoy related class:&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# config/initializers/ahoy.rb

class Ahoy::Store &amp;lt; Ahoy::Stores::ActiveRecordStore
  def track_event(name, properties, options, &amp;amp;block)
    event =
      event_model.new do |e|
        e.id = options[:id]
        e.visit_id = ahoy.visit_id
        e.user = user if e.respond_to?(:user=)
        e.name = name
        e.properties = properties.to_json # the keypoint
        e.time = options[:time]
      end

    yield(event) if block_given?

    begin
      event.save!
    rescue *unique_exception_classes
      # do nothing
    end
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;The key point is the track_event, just override it and convert the type for properties assignment.&lt;/p&gt;
</content>
    <author>hothero</author>
  </entry>
  <entry>
    <id>tag:blog.hothero.org,2005:Post/502889</id>
    <published>2016-02-13T09:29:00+08:00</published>
    <updated>2016-02-13T09:36:50+08:00</updated>
    <link rel="alternate" type="text/html" href="http://blog.hothero.org/posts/2016/02/13/gu-gexin-services-google-cloud-functions"/>
    <title>谷哥新服務：Google Cloud Functions</title>
    <content type="html">&lt;p&gt;&lt;a href="http://venturebeat.com/2016/02/09/google-has-quietly-launched-its-answer-to-aws-lambda/"&gt;外媒&lt;/a&gt;表示：Google 默默開始了 Google Cloud Functions 產品，一個要與 AWS Lambda 相較的服務，雖然還在 Alpha 版。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Google Cloud Functions is a lightweight, event-based, asynchronous compute solution that allows you to create small, single-purpose functions that respond to cloud events without the need to manage a server or a runtime environment”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;不過目前只支援 Node.js 的模組，也就是只能用 javascript 去撰寫，不像 AWS Lambda 還有 java 與 python 可以選擇。&lt;/p&gt;

&lt;p&gt;Microsoft 的 Azure 聽說也正在開發，拭目以待&lt;/p&gt;
</content>
    <author>hothero</author>
  </entry>
  <entry>
    <id>tag:blog.hothero.org,2005:Post/500303</id>
    <published>2016-02-12T20:32:00+08:00</published>
    <updated>2016-02-12T21:51:24+08:00</updated>
    <link rel="alternate" type="text/html" href="http://blog.hothero.org/posts/2016/02/12/rails4-patterns-iii-concerns"/>
    <title>Rails4 Patterns III: Concerns</title>
    <content type="html">&lt;p&gt;Concerns 是 Rails 拿來做模組化的一個方式，在 Rails4 之後也正式在 app/models/ 內有了 concerns 這麼一個資料夾的一席之地，也是一種 Rails Convention。&lt;/p&gt;
&lt;h3&gt;把重複的 Model 程式碼搬到 Model Concerns 內&lt;/h3&gt;
&lt;p&gt;留言功能在臉書不斷的演進下，似乎已經成為現在網站的一個基本盤功能，什麼內容都可以討論一下。Rails4 Patterns 裡面拿這個當做範例，覺得蠻適合的。&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/models/post.rb
class Post &amp;lt; ActiveRecord::Base
  has_many :comments, as: :commentable
  def comments_by_user(id) # 重複了
    comments.where(user_id: id)
  end
end

# app/models/image.rb
class Image &amp;lt; ActiveRecord::Base
  has_many :comments, as: :commentable
  def comments_by_user(id) # 重複了
    comments.where(user_id: id)
  end
end

# app/models/comment.rb
class Comment &amp;lt; ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;以上方來說利用了 Rails 內的 Polymorphic Association 的特性來做到文章跟圖片都可以留言，但也因如此，造成文章與圖片的 Model 出現重複程式碼在所難免。&lt;br&gt;
若是包成 Concern 的方式來做，不只可以重複使用、避免重複程式碼，更可做到模組化。&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/models/concerns/commentable.rb 
module Commentable
  def self.included(base) # 這邊還可以更優化
    base.class_eval do
      has_many :comments, as: :commentable
    end
  end

  def comments_by_user(id)
    comments.where(user_id: id)
  end
end

# app/models/post.rb
class Post &amp;lt; ActiveRecord::Base
  include Commentable
end
# app/models/image.rb
class Image &amp;lt; ActiveRecord::Base
  include Commentable
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;純以 Ruby 寫法來說這樣包成 Module 完全沒有問題，但若善用 Rails 內建的 Library 會更好&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/models/concerns/commentable.rb
module Commentable
  extend ActiveSupport::Concern # 提供了 included 等的方法，更方便將 Module 包裝成 ActiveRecord 的擴充
  
  included do
    has_many :comments, as: :commentable
  end
  
  module ClassMethods
  # 因為 ActiveSupport::Concern 的關係，讓這裡面的 ClassMethods 可以直接變成引用的 class 內的 class method
  # 像是 Image.upvote(@comment)
    def upvote(comment)
      # ...
    end
  end
  
  def comments_by_user(id)
    comments.where(user_id: id)
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;Controller 一樣也有 Concerns&lt;/h3&gt;
&lt;p&gt;相似性質的 Resource 操作也容易出現重複程式碼&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/controllers/images_controller.rb
class ImagesController &amp;lt; ApplicationController
  def show
    @image = Image.find(params[:id])
    file_name = File.basename(@image.path) # 重複了
    @thumbnail = "/thumbs/#{file_name}"
  end
end

# app/controllers/videos_controller.rb
class VideosController &amp;lt; ApplicationController
  def show
    @video = Video.find(params[:id])
    file_name = File.basename(@video.path) # 重複了
    @thumbnail = "/thumbs/#{file_name}"
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;包成 Controller Concern&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/controllers/concerns/previewable.rb
module Previewable
  def thumbnail(attachment)
    file_name = File.basename(attachment.path)
    "/thumbs/#{file_name}"
  end
end

# app/controllers/images_controller.rb
class ImagesController &amp;lt; ApplicationController
  include Previewable
  def show
    @image = Image.find(params[:id])
    @thumbnail = thumbnail(@image)
  end
end

# app/controllers/videos_controller.rb
class VideosController &amp;lt; ApplicationController
  include Previewable
  def show
    @video = Video.find(params[:id])
    @thumbnail = thumbnail(@video)
  end
 end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;從這些範例與程式碼演進過程，也可以知道一個小知識。就是 concerns 跟 ActiveSupport::Concern 是不一樣的，concerns 是指 Rails4 裡面將重複程式碼模組化的資料夾位置，ActiveSupport::Concern 是從 Rails3 開始，當做是 module 包成擴充可使用的方法。&lt;/p&gt;
</content>
    <author>hothero</author>
  </entry>
  <entry>
    <id>tag:blog.hothero.org,2005:Post/489138</id>
    <published>2016-02-08T17:49:00+08:00</published>
    <updated>2016-02-08T23:48:05+08:00</updated>
    <link rel="alternate" type="text/html" href="http://blog.hothero.org/posts/2016/02/08/rails-patterns-class-methods-scopes"/>
    <title>Raiils4 Patterns II: Class Methods and Scopes</title>
    <content type="html">&lt;h3&gt;Extracting Queries&lt;/h3&gt;
&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/controllers/posts_controller.rb
class PostsController &amp;lt; ApplicationController
  def index
    @posts = Post.where('published = ? AND published_on &amp;gt; ?', true, 2.days.ago)
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;這是 controllers 內會常見的程式碼，以結果來說完全沒問題，但考量到維護性與可讀性，有可以改進的地方：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 controllers 內做了太多細節&lt;/li&gt;
&lt;li&gt;容易產生不必要重複的程式碼&lt;/li&gt;
&lt;li&gt;測試較難撰寫&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;改進後&lt;/strong&gt;&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/controllers/posts_controller.rb
class PostsController &amp;lt; ApplicationController
  def index
     @posts = Post.recent
  end
end

# app/models/post.rb
class Post &amp;lt; ActiveRecord
  def self.recent
    where('published = ? AND published_on &amp;gt; ?', true, 2.days.ago)
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;這是標準改進作法，使用了 ActiveRecord 的 scope 特性，有其他作法嗎？有的，可以使用以往 Class Methods 的方式&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;#app/models/post.rb
class Post &amp;lt; ActiveRecord
  def self.recent
    where('published = ? AND published_on &amp;gt; ?', true, 2.days.ago)
  end
  
  def self.by_author(author)
    where(author: author)
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;一樣都是可以被串連起來使用的沒有問題！&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;author = "Carlos"
Post.by_author(author).recent
# SELECT "posts".* FROM "posts" WHERE "posts"."author" = 'Carlos' AND (published_on &amp;gt; '...')
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;!--more--&gt;
&lt;h3&gt;那到底差在哪？嘗試考量更多的狀況&lt;/h3&gt;
&lt;p&gt;假設說好的要找作者是誰，但是你不跟我說是誰怎麼辦？&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# params[:author] #=&amp;gt; nil
Post.by_author(params[:author]).recent
# SELECT "posts".* FROM "posts" WHERE "posts"."author" IS NULL AND (published_on &amp;gt; '...')

# model 改寫，如果沒傳資料就回傳 nil
# app/models/post.rb
class Post &amp;lt; ActiveRecord
  def self.by_author(author)
    where(author: author) if author.present?
  end
 
  def self.recent
    where('published_on &amp;gt; ?', 2.days.ago)
  end
end

# 實際使用時若遇到 nil，就沒辦法串連 queries 了
# params[:author] #=&amp;gt; nil
Post.by_author(params[:author]).recent
# No MethodError: undefiend method 'recent' for NilClass

# 通常會試圖去改 model 的 class method 試圖不要讓 NilClass 錯誤發生
# app/models/post.rb
class Post &amp;lt; ActiveRecord
  def self.by_author(author)
    if author.present?
      where(author: author)
    else
      all # 沒資料就列出全部
    end
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;看起來 class method 可以做到跟 scope 一樣的事情，還可依照需求提供 edge case 客製化的結果。但以團隊整體開發默契，以及 Rails Convention 來說，若非特殊狀況，還是建議乖乖使用 scope 就好，這樣一來一往就差了6行程式碼！&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/models/post.rb
class Post &amp;lt; ActiveRecord
  scope :by_author, -&amp;gt;(author) { where(author: author) if author.present? }
  scope :recent, -&amp;gt; { where('published_on &amp;gt; ?', 2.days.ago) }
end

# scopes always return a chainable object
Post.by_author(nil).recent
# SELECT "posts".* FROM "posts" WHERE (published_on &amp;gt; '...')
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;更高階用法，合併 Scopes&lt;/h3&gt;
&lt;p&gt;專案內絕對不只一個 Model 而已，Model 數量與程式碼重複的機率成正比，甚至可能會變成指數成長，還有招嗎？&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;class Comment &amp;lt; ActiveRecord
  belongs_to :post
  scope :approved, -&amp;gt;{ where(approved: true) }
end
# same query logic duplicated on two different models
   
class Post &amp;lt; ActiveRecord
  has_many :comments
  scope :with_approved_comments,
    -&amp;gt; { joins(:comments).where('comments.approved = ?', true) }
end
# "SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE ("comments"."approved" = 't')
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;這時候可以善用&lt;strong&gt;Merge&lt;/strong&gt;的功能&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/models/post.rb
scope :with_approved_comments,
    -&amp;gt; { joins(:comments).where('comments.approved = ?', true) }
# 改成用 merge 即可！
scope :with_approved_comments,
    -&amp;gt; { joins(:comments).merge(Comment.approved) }
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;Rails3 與 Rails4 的 Query Chaining 也不同！&lt;/h3&gt;
&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;class User &amp;lt; ActiveRecord::Base
  scope :active,   -&amp;gt; { where(state: "active") }
  scope :inactive, -&amp;gt; { where(state: "inactive") }
end

# 如果是 Rails3，Query 接在一起會以最後一個為主
User.active.inactive # in Rails3
# SELECT * FROM users WHERE state = 'inactive'

# 如果是 Rails4，Query 接在一起會以 Append 為主
User.active.inactive # in Rails4
# SELECT * FROM posts WHERE state = 'active' AND state = 'inactive'
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;所以在 Rails4 若有 Query 可能會有衝突，也就是條件式的依據一樣，可以採用&lt;strong&gt;merge&lt;/strong&gt;來做&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;User.active.merge(User.inactive)
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;整體來說 merge 這方法還是略嫌粗糙，不大符合 ruby 簡潔直觀的程式碼，有點曲折。如果可以是 User.active.inactive.merged 之類的，而不是帶參數進去的話就還不錯 XDDD&lt;/p&gt;
</content>
    <author>hothero</author>
  </entry>
  <entry>
    <id>tag:blog.hothero.org,2005:Post/483378</id>
    <published>2016-02-06T23:54:00+08:00</published>
    <updated>2016-02-07T00:47:20+08:00</updated>
    <link rel="alternate" type="text/html" href="http://blog.hothero.org/posts/2016/02/06/rails4-patterns-models-i"/>
    <title>Rails4 Patterns I: Models</title>
    <content type="html">&lt;p&gt;今天起春節希望每天介紹 Rails4 Patterns 內的一個章節給大家，這是一個在 Code School 的課程，原址：&lt;a href="https://www.codeschool.com/courses/rails-4-patterns" rel="nofollow" target="_blank"&gt;https://www.codeschool.com/courses/rails-4-patterns&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;肥肥的 Controller 是不好的&lt;/h3&gt;
&lt;p&gt;原因有幾個：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;會難以理解&lt;/li&gt;
&lt;li&gt;商業邏輯難以統整&lt;/li&gt;
&lt;li&gt;程式碼間容易衝突&lt;/li&gt;
&lt;li&gt;難以開發新功能&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;像是以下程式碼&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;class ItemsController &amp;lt; ApplicationController
  def publish
    if @item.is_approved?
      @item.published_on = Time.now

      if @item.save
        flash[:notice] = "Your item published!"
      else
        flash[:notice] = "There was an error."
      end
    else
      flash[:notice] = "There was an error."
    end

    redirect_to @item
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;當時間的推進，應用的發展，條件式只會越來越複雜&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;if @item.is_approved? # 原先的條件式
# -&amp;gt;
if @item.is_approved? &amp;amp;&amp;amp; @iterm.user != "Loblaw" # 新增條件
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;這裡切記一個原則：「&lt;strong&gt;Tell, Don't Ask&lt;/strong&gt;」，應該要直接的告訴物件它要做什麼事情。而不是透過問句的方式去確認目前它的狀態是什麼後，再決定作什麼事情。&lt;/p&gt;
&lt;h3&gt;商業邏輯該丟到 Models 內&lt;/h3&gt;
&lt;p&gt;如剛剛的 controller，我們可以將邏輯性的功能包裝丟至 model 內，那對 controller 而言就可以符合 &lt;strong&gt;Tell, Don't Ask&lt;/strong&gt; 原則。&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/controllers/items_controller.rb
class ItemsController &amp;lt; ApplicationController
  def publish
    if @item.publish # 明確知道這邊的動作是將 item 發佈
      flash[:notice] = "Your item published!"
    else
      flash[:notice] = "There was an error."
    end

    redirect_to @item
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/models/item.rb
class Item &amp;lt; ActiveRecord::Base
   belongs_to :user

   def publish
     if !is_approved? || user == 'Loblaw'
       return false
     end

     self.published_on = Time.now
     self.save
   end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;函式為確保是簡潔與可讀性的程式碼，特別將 if-else 的邏輯單純化，如果一個 if 就能乾淨俐落，為什麼還要來個 else，大家都愛直線，不要拐彎抹角阿！&lt;/p&gt;

&lt;p&gt;所以這邊才會是&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;if !is_approved? || user == 'Loblaw'
  return false
end
...
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;而非&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# wrong case
if is_approved? || user != 'Loblaw'
  ...
else
  return false
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;避免在 Callbacks 中呼叫其他功用的物件&lt;/h3&gt;
&lt;p&gt;因為 callback 本身就像是一個物件生命週期的一個環節，如果牽扯到其他的物件，不只讓人在看程式碼會覺得有點跳，甚至因為交流密切，間接影響到物件的資料庫運作週期。&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;class User &amp;lt; ActiveRecord::Base
  before_create :set_token

  protected
    def set_token
      self.token = TokenGenerator.create(self) # another class object
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h5&gt;為此建立一個客製化的函式&lt;/h5&gt;
&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/models/user.rb 
class User &amp;lt; ActiveRecord::Base
  def register # 另外拉一個函式，專作註冊
    self.token = TokenGenerator.create(self)
    save
  end
end

# app/controllers/users_controller.rb
class UsersController &amp;lt; ApplicationController
  def create
    @user = User.new(user_params)
    if @user.register # 使用客製化的函式
      redirect_to @user, notice: 'Success'
    else
      ...
    end
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h5&gt;Callbacks 應該是用來作為更改欄位值或其他內在因素為主&lt;/h5&gt;
&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/models/user.rb
class User &amp;lt; ActiveRecord::Base
  before_create :set_name

  protected
    def set_name
      self.name = self.login.capitalize if name.blank?
    end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;也要特別留意，callbacks 放在 protected 內是 Rails 的 Convention，務必要遵守&lt;/p&gt;
&lt;h3&gt;不是所有的 Models 都一定要是 ActiveRecord&lt;/h3&gt;
&lt;p&gt;以被檢舉的使用者需要的停權動作為例，裡面牽扯到許多要考慮的層面，例如停權後就不能發言、不能登入等等&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/controllers/users_controller.rb
class UsersController &amp;lt; ApplicationController
  def suspend
    @user = User.find(params[:id])
    @user.suspend!
    
    redirect_to @user, notice: 'Successfully suspended.'
  end
end

# app/models/user.rb
class User &amp;lt; ActiveRecord::Base
  has_many :items
  has_many :reviews
  
  def suspend!
    self.class.transaction do # transaction 包起來可以讓這些動作對於資料庫來說只是一件事情
      self.update!(is_approved: false)
      self.items.each { |item| item.update!(is_approved: false) }
      self.reviews.each { |review| review.update!(is_approved: false) }
    end
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;以上例來說，問題是&lt;strong&gt;同個函式內包含太多的邏輯&lt;/strong&gt;&lt;/p&gt;
&lt;h5&gt;同個 class 裡面太多邏輯，可以拆成各自負責的工作。也要注意函式牽扯到太多其他的物件，可能會導致「God Object」現象發生，就是指作太多事情的意思。&lt;/h5&gt;
&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;class User &amp;lt; ActiveRecord::Base
  ...
  def suspend!
    self.class.transaction do
      disapprove_user!
      disapprove_items!
      disapprove_reviews!
    end
  end
  
  def disapprove_user!
  def disapprove_items!
  def disapprove_reviews!
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h4&gt;抽象化：不是所有跟使用者有關係就得都在使用者的 Model 內&lt;/h4&gt;
&lt;p&gt;以停權使用者這件事情來說，抽象化有幾個好處：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PORO: Plain Old Ruby Object&lt;/li&gt;
&lt;li&gt;只需一個正規的 instance method&lt;/li&gt;
&lt;li&gt;這個類別就只需要專注一件事情就好&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# app/models/user_suspension.rb
class UserSuspension
  def initialize(user)
    @user = user
  end

  def create!
    @user.class.transaction do
      disapprove_user!
      disapprove_items!
      disapprove_reviews!
    end
  end
  
  private
    def disapprove_user!
    def disapprove_items!
    def disapprove_reviews!
end

# app/controllers/users_controller.rb
class UsersController &amp;lt; ApplicationController
  def suspend
    @user = User.find(params[:id])
    suspension = UserSuspension.new(@user)
    suspension.create!
    
    redirect_to @user, notice: 'Successfully suspended.'
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;其他常見的抽象化：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UserRegistration&lt;/li&gt;
&lt;li&gt;PlanSubscription&lt;/li&gt;
&lt;li&gt;CreditCardCharge&lt;/li&gt;
&lt;li&gt;ShoppingCart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;小結一下，所以常會使用非 ActiveRecord 的方式將獨立且唯一的商業邏輯搬出來成一個新的類別。&lt;/p&gt;
</content>
    <author>hothero</author>
  </entry>
  <entry>
    <id>tag:blog.hothero.org,2005:Post/301357</id>
    <published>2015-09-26T09:09:00+08:00</published>
    <updated>2015-09-26T09:09:59+08:00</updated>
    <link rel="alternate" type="text/html" href="http://blog.hothero.org/posts/2015/09/26/how-to-gemify-assets-for-rails-in-4-steps"/>
    <title>How to Gemify Assets for Rails in 4 Steps</title>
    <content type="html">&lt;p&gt;把 assets 包成 rails 的 gem 非常簡單，只要簡單4個步驟！（更多 rails assets gems 請參照 &lt;a href="https://rails-assets.org/"&gt;rails-assets&lt;/a&gt;）&lt;/p&gt;
&lt;h3&gt;1. Create Gem Framework&lt;/h3&gt;
&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;bundle gem  momentjs-rails
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;2. Start Your Engine&lt;/h3&gt;
&lt;p&gt;bundler 產生的 gem 是一個標準 ruby 模組，但我們希望他是 rails engine&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# lib/momentjs-rails.rb

require "momentjs-rails/version"

module Momentjs
  module Rails
    class Engine &amp;lt; ::Rails::Engine
    end
  end
end
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;3. Add The Assets&lt;/h3&gt;
&lt;p&gt;下載需要放進 gem 的 assets，建議放沒有壓縮也沒有編譯過的版本，因為可以讓 assets-pipeline 來幫我們做壓縮就好。&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;mkdir -p vendor/assets/javascripts
cp ~/Downloads/moment.js vendor/assets/javascripts/moment.js
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;補充說明：app/assets, lib/assets, vendor/assets 有什麼不同？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;app/assets: 一般在開發調整的 js, css（會一直修正的）&lt;/li&gt;
&lt;li&gt;lib/assets: 自有專案內打包模組化的 js, css（例如：backme-bootstrap.js）&lt;/li&gt;
&lt;li&gt;vendor/assets: 第三方的 js, css，不會去變動的（例如：bootstrap.js）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. Complete The Gemspec&lt;/h3&gt;
&lt;p&gt;裡面的 TODOs 都要解決，不然會沒辦法成功的編譯成 gem，然後拿掉 spec.executables 跟 spec.test_files 這兩行，因為裡面沒有 .rb 檔案需要被執行，只是單純打包起來，所以用不到。&lt;/p&gt;

&lt;p&gt;把 spec.files 改掉：&lt;/p&gt;

&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;spec.files = Dir["{lib,vendor}/**/*"] + ["MIT-LICENSE", "README.md"]
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;接著再下 gem build momentjs-rails.gemspec 即可完成。&lt;/p&gt;
&lt;h3&gt;實際使用&lt;/h3&gt;
&lt;figure class="figure-code code"&gt;&lt;figcaption&gt;&lt;span&gt;
&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;pre&gt;# Gemfile
gem "momentjs-rails", path: "{your gem file directory}"

# app/assets/javascripts/application.js
//= require moment
&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;外譯：&lt;a href="http://www.prioritized.net/blog/gemify-assets-for-rails/"&gt;Gemify Assets for Rails&lt;/a&gt;&lt;/p&gt;
</content>
    <author>hothero</author>
  </entry>
</feed>
