21 days ago

Mocking

在這個例子裡面,我們用了 mock 手法,確認 show 這個 action,真的有去呼叫 Suggestion.find

  • allow(Suggestion).to receive(:find).and_return(suggestion)
  • expect(Suggestion).to receive(:find)
  describe "GET show" do

    it "assigns @course and render show" do
      suggestion = double(:to_param => "123" )
      allow(Suggestion).to receive(:find).and_return(suggestion)
      expect(Suggestion).to receive(:find)

      get :show, :id => suggestion.to_param

      expect(response).to render_template :show
      expect(assigns[:suggestion]).to eq(suggestion)

    end

  end
class SuggestionController
  def show
    @suggestion = Suggestion.find(params[:id])
  end
end

劣勢:順序「反直覺」

但是其實這樣的寫法,對於我們之前提到的 Four-Phase Test:

  • Setup (準備測試資料)
  • Exercie (實際執行測試)
  • Verification (驗證測試結果)
  • Teardown (拆解測試)

有點相違背了。

我們先做了 Verification (expect)再做 Exercise ( get :show) 。

劣勢:容易失敗

而且在拆解階段,我們會將這段測試重寫成這樣。將 expect 寫在 before 階段,但這樣寫的缺點是,expect 如果不如預期,後面測試會因為 except 全死。

  describe "GET show" do

    before do 
      @suggestion = double(:to_param => "123" )
      allow(Suggestion).to receive(:find).and_return(@suggestion)
      expect(Suggestion).to receive(:find)
      get :show, :id => @suggestion.to_param
    end

    it { expect(response).to render_template :show }
    it { expect(assigns[:suggestion]).to eq(@suggestion)}
  end

Spying

與其使用 Mocking 手法,我們可以改用 Spy 手法,將測試改成這樣。(RSpec 使用 have_received 去驗證)

  describe "GET show" do

    before do 
      @suggestion = double(:to_param => "123" )
      allow(Suggestion).to receive(:find).with(@suggestion.to_param).and_return(@suggestion)
      get :show, :id => @suggestion.to_param
    end

    it { expect(response).to render_template :show }
    it "should find suggestion and assign suggestion" do 
       expect(Suggestion).to have_received(:find).with(@suggestion.to_param)
       expect(assigns[:suggestion]).to eq(@suggestion)
    end

  end

Four Phase 順序不但正確,而且是在 Exercise 後,才做 Verification。

 
21 days ago

double 可以讓我們輕易地閃掉準備「協作者」的痛苦。但是有時候,我們還是可能被「協作者」本身的內部邏輯改變整到。比如說,這裡是一個「建議」Suggestion 表單:

require 'rails_helper'

RSpec.describe SuggestionsController, type: :controller do

  describe "POST create" do 
    context "when suggestion is invalid" do 
      it "render new" do 
        post :create, :suggestion => { :title => "", :description => "" }

        expect(response).to render_template :new
      end
    end
  end
end

class Suggestion < ActiveRecord::Base
   validates :title, presence: true
end

class SuggestionsController 
  def create
    @suggestion = Suggestion.new(suggestion_params)
    if @suggestion.save
      redirect_to root_path
    else
      render :new
    end
  end

  protected

  def suggestion_params
    params.require(:suggestion).permit(:title, :description)
  end

end

如果物件非法的話,要 render new 這個 action。正常來說,這樣的 controller 測試,應該會通過。

但是有時候 controller 程式碼沒有動,但 model 程式碼內部動了,比如說在這個例子,可能多加了新的欄位,或者是新增了其他的驗證方式,導致這個 controller 測試要因為 suggestion 內部本身的邏輯要修改。

這樣真的很討厭。

重寫測試

其實這裡我們根本不應該塞「例子」進去,這樣 suggestion 內部邏輯一旦改變,我們的 controller test 就要改個沒完。我們真正應該要做的是:

  • 準備一個「一定儲存失敗的替身」
  • 然後在 controller 讓 @suggestion.save 鐵定失敗
  • 因為我們要驗證的是 「render :new」

所以我們可以把測試改寫成這樣

require 'rails_helper'

RSpec.describe SuggestionsController, type: :controller do

  describe "POST create" do 
    context "when suggestion is invalid" do 
      it "render new" do 
        invalid_suggestion = double(:save => false) 
        allow(Suggestion).to receive(:new).and_return(invalid_suggestion)

        post :create, :suggestion => { :xxx => "yyy" }

        expect(response).to render_template :new
      end
    end
  end
end

這樣 suggestion 內部驗證再怎麼樣變,都不會影響到這個 controller test。

 
21 days ago

Four-Phase Test 是一種常見的測試 Pattern。幾乎也是一般人在寫測試的手法,依序為:

  • Setup (準備測試資料)
  • Exercie (實際執行測試)
  • Verification (驗證測試結果)
  • Teardown (拆解測試)

但是通常我們在一般寫測試或者是 TDD 時,常常會遇到一些狀況,導致 Setup 階段時,資料「難以被準備」。通常是有以下幾種狀況:

對象物件當中的「協作者」還沒被誕生

比如說都還沒寫到那個 class 或者是 method

對象物件當中的「協作者」是「系統外部物件」

如: API 回傳內容

要準備的資料、相依性過於複雜

要生超多物件才能測試

執行這個測試,呼叫到「吃效能很兇」的 method

導致測試運行緩慢

使用替身

但我們的首要要務,其實是要測我們內部的程式的邏輯,不是要測「外部邏輯」的狀況。所以我們可以使用 mock objects,在 RSpec 裡面叫做 double(替身)。

在 Setup 階段使用「替身」的資料,直接進行測試。

舉例來說,我這裡有一個學生計算器

class StudentsCalculator

  def initialize(course)
    @course = course
  end

  def students_count
    @course.students_count
  end
end

我還沒想好 course.students_count 要怎樣設計(這在 TDD 中很常見),但是我知道在 StudentsCalculator 中的邏輯,吃的就是將來 course 提供的 students_count,而且「我現在就想要驗證這件事」。所以我可以假造一個 course 的替身,讓這段測試可以通過。


require 'rails_helper'

RSpec.describe StudentsCalculator do
  describe "#students_count" do 
    it "returns students count" do 
      course = double(:students_count => 5 )
      student_calculator = StudentsCalculator.new(course)
      expect(student_calculator.students_count).to eq(5)
    end
  end
end

 
21 days ago

以這個程式來說,我們要把「課程上架」,課程上架以後,系統會送一封 onboarding 信給開課教師。

class Course
  belongs_to :user

  def published?
    published_at.present?
  end

  def publish!
    user.send_welcome_email!(self)
    update_column(:published_at, Time.now)
  end

end
class User < ActiveRecord::Base

  has_many :courses    

  def send_welcome_email!(course)
    UserMailer.send_course_welcome(course)
  end
end

但是下面這個測試,我們在這裡測試的狀況,我們根本不在乎「信是否有沒有被送給開課教師」,我們在乎的是:呼叫 publish! 後,是否課程真的已經會被上架。

所以我們就會用 allow(course.user).to receive(:send_welcome_email!)send_welcome_email! 這件事 stub 掉,讓它 return nil,這樣就不會呼叫 UserMailer 了。

何況,我們可能也還沒時間開發 UserMailer 內的東西。

  describe "#publish!" do 

    let(:user) { FactoryGirl.create(:user) }
    let(:course) { FactoryGirl.create(:course, :user => user ) }

    it "will be publsihed" do 
      allow(course.user).to receive(:send_welcome_email!)
      course.publish!
      expect(course).to be_published
    end
  end
  • 備註: 在 RSpec 2.x 版,你可以直接這樣寫 course.stub(:send_welcome_email!),但這寫法造成一些敘述語法問題,所以 RSpec 3 改成 allow + receive
  • 備註: 在 RSpec 2.x 的 stubmock 語法造成一些很大的語法與觀念問題。RSpec 3 改的清楚不少...
 
21 days ago

Stub

一般來說,驗證「回傳值」或驗證「狀態改變時」,經常會使用 Stub 手法。
因為在這兩種狀況中,即便這個 method 有可能去呼叫「外部物件」,坦白來說,我們「不在乎」。


比如說這段程式碼

class Post
  def visit!
    update_visit_cache_to_memory!
    self.increment_counter(:visit_count, 1)
  end

  def current_visit_count
    update_visit_cache_to_memory!
    return visit_count
  end
end

因為我們只在乎「當下這個物件」。所以寫測試時,我們會 stub 掉 update_visit_cache_to_memory!「呼叫 外部 method」這件事。以取得我們要的結果。

Mock

「模擬」與一個「協作者」的互動,設立一個「會收到指定訊息」的期望,去驗證互動是否真的有發生。

 
21 days ago

要開始講解 Stub 與 Mock 這兩個概念之前。我想要先來介紹單元測試的種類,這樣才講得清楚後續要繼續寫下去的主題。

一般來說,單元測試分成三種狀況。

1. 驗證回傳值是否符合期待

查詢:

  • 回傳某些東西
  • 不改變狀態

2. 驗證物件狀態的改變是否符合期待

命令:

  • 改變內部狀態

3. 驗證是否有去執行與外部的呼動

呼叫:

  • 是否有去執行與外部的互動

 
21 days ago

很多新手在經歷一兩年的開發經驗後,開始覺得寫測試很重要。想要入門學習測試。但是看到這些名詞:

  • stub
  • mock
  • double
  • allow
  • receive
  • expect

就覺得很頭痛。特別是 RSpec 2.1 與 RSpec 3 一些語法升級,又造成了更大的混淆。所以我希望寫一系列的文章,解釋清楚這些名詞。想辦法把測試這個主題講清楚。

這一個系列,我會嘗試以以下的順序講解以下主題:

 
22 days ago

有同學最近在問 RSpec 的問題,因為會回答很多次,乾脆寫「一個經典例子」,來介紹 RSpec 的常見 syntax,來介紹一串重要的手法。

例子:Course#publish?

publish? 的判斷方式是 published_at 這個欄位存在與否。

class Course < ActiveRecord::Base
    
  def published?
    published_at.present?
  end
end

觀念 1: 測試要寫 正 / 負場景

在這個例子裡面,因為是測 boolean 值,比較好的方式,是要寫正反例子。

RSpec.describe Course, type: :model do
   describe "#publish?" do 
    it "return true when published_at present?" do 
      course = FactoryGirl.create(:course, published_at: Time.now)
      expect(course.published?).to be_truthy
    end

    it "return false when published_at blank?" do 
      course = FactoryGirl.create(:course)
      expect(course.published?).to be_falsey
    end
  end
  
end

觀念 2 :當 when 與重複語句出現,應抽出變成 context

  • when published_at present?
  • when published_at blank?

這兩句是場景,所以接下來可以改寫成

RSpec.describe Course, type: :model do
  describe "#publish?" do 

    context "when published_at present?" do 
      it "return true" do 
        course = FactoryGirl.create(:course, published_at: Time.now)
        expect(course.published?).to be_truthy
      end
    end

    context "when published_at blank?" do 
      it "return false" do 
        course = FactoryGirl.create(:course)
        expect(course.published?).to be_falsey
      end
    end

  end
end

觀念 3 : 用 let 抽出 course,it 內只測 expect

將物件抽到 let。

RSpec.describe Course, type: :model do
describe "#publish?" do 

    context "when published_at present?" do 
      let(:course) { FactoryGirl.create(:course, published_at: Time.now) }
      it "return true" do 
        expect(course.published?).to be_truthy
      end
    end

    context "when published_at blank?" do 
      let(:course) { FactoryGirl.create(:course) }
      it "return false" do 
        expect(course.published?).to be_falsey
      end
    end

  end
end

觀念 4 : Minimal Valid Object 與「測試真正的變因」

但是在步驟 3 中,這樣的測試手法,是生成了「兩個不同的物件」去測。但我們寫這個測試的目的是要「測試一個物件,在published_at值不同」的情況下的回傳狀況。

真正的「宿主」是 Coruse 本身。所以我們改用 subject。傳 nil 與 現在的時間,去測試真正要測的行為。

RSpec.describe Course, type: :model do
  describe "#publish?" do 

    subject(:course) { described_class.new :published_at => published_at } 
    context "when published_at present?" do 
      let(:published_at) { Time.now }
      it "return true" do 
        expect(course.published?).to be_truthy
      end
    end
    context "when published_at blank?" do 
      let(:published_at) { nil }
      it "return false" do 
        expect(course.published?).to be_falsey
      end
    end

  end
  
end

觀念 5 : xxxx? 可以使用 be_xxxxx 去測試

  describe "#publish?" do 

    subject(:course) { described_class.new :published_at => published_at } 
    context "when published_at present?" do 
      let(:published_at) { Time.now }
      it "return true" do 
        expect(course).to be_published
      end
    end
    context "when published_at blank?" do 
      let(:published_at) { nil }
      it "return false" do 
        expect(course).not_to be_published
      end
    end

  end

觀念 6 :is_expected

測 return true/ false 看起來像是超多餘的。而且我們是對 subject 測,所以又可以改成 is_expected

RSpec.describe Course, type: :model do
  describe "#publish?" do 

    subject(:course) { described_class.new :published_at => published_at } 

    context "when published_at present?" do 
      let(:published_at) { Time.now }
      it { is_expected.to be_published }
    end
    context "when published_at blank?" do 
      let(:published_at) { nil }
      it { is_expected.not_to be_published }
    end
  end
  
end

最後看起來是不是變得很好維護呢?

 
about 1 month ago

之前不少讀者都問過我一個共有的問題:就是如果它的產品很新,或者它的 TA 很小眾,要怎麼開始做 Growth / Marketing。我的建議都是請他們先「實作 Content Marketing」。

What is Content Marketing ?

現在網路上的用戶,每天都面臨著「廣告轟炸」。基本上如果你的產品很新或 TA 很小眾,下廣告基本上也沒什麼用。使用者通常也會跳過。

減肥粉絲團

更多人的購買決策是,觀看部落格心得、看別人推薦而購買。或者長期訂閱某粉絲團的內容,對該粉絲團產生信賴,進而購買這個粉絲團上面推薦的內容。

電子書下載

又或者我講一件更令你熟悉的場景。有天你逛到某一個電子書免費下載的頁面,輸入 email 就能下載該本電子書,然後你的電子信箱裡面就開始收到對方寄得一些厲害的 Tips,因為也很有趣,所以你沒退掉的意思。某天該公司推薦一個軟體,你覺得還不錯,就買了....

這就是「Content Markerting」做的事。

Content Marketing 的形式

Content Marketing 的技巧與策略有很多種。比較常見的格式是

  • 養粉絲團分享文章
  • 寫部落格(關鍵字)
  • 製作免費電子書
  • 線上研討會

See / Think / Do framework

本篇只是一篇 Content Marketing 的 Intro。我也沒辦法 Cover 太多的詳盡步驟。在這邊我想直接以製作部落格內容為例子,講解「好的」Content Marketing 要怎麼做。

這裏我想介紹的是一個簡易的 Framewrok 叫做 See / Think / Do。這個框架是一個在 Google 上班的行銷人 Avinash Kashykk 研發的。

See / Think / Do 框架,將所有觀眾分為三種人。也是一個漏斗架構

  • See 群眾,但他們可能沒有購買意願
  • Think 他們可能表達了一點購買意願
  • Do 已經傳達了非常清楚的購買意願

在這裡我舉兩個我喜歡的兩套 SaaS 軟體,來讓各位更了解這是怎麼一回事。

例子 1 : Codeclimate

Codeclimate 是一家用來檢測「軟體品質」的軟體公司,基本上 Ruby on Rails 界的人,人人都知道這間公司。它的功用就是你將程式碼上傳,Codeclimate 會自動檢測你的程式碼裡面的「壞味道」「安全漏洞」,將你的程式碼評級。

它的 See / Think / Do 群眾就是

  • See : 有在寫程式的人
  • Think : 認為把程式寫乾淨有點重要的人
  • Do : 認為團隊的程式「絕對要乾淨」的資深程式設計師,正在找工具讓團隊的程式碼品質更佳的人。

例子 2 : GrooveHQ

Groove 是一家線上客服軟體公司。基本上可以把客戶寄來的 Support 信,自動轉成 ticket,讓公司內客服協作,也提供 FAQ site 功能。我們公司就是用 Groovq 做客服管理。

它的 See / Think / Do 群眾就是

  • See : 有在做生意的人
  • Think : 認為做好客服有點重要的人
  • Do : 認為「客服好」等於「賺大錢」的老闆。正在找工具,讓公司客服回覆速度更快的人。

Why See / Think / Do 框架重要?

很多人做「內容行銷」,花了很多力氣在「Do」這個層面的客群。

甚至認為 Do 才是最正確的客群。

  • Do : 認為團隊的程式「絕對要乾淨」的資深程式設計師,正在找工具讓團隊的程式碼品質更佳的人。
  • Do : 認為「客服好」等於「賺大錢」的老闆。正在找工具,讓公司客服回覆速度更快的人。

所以方向也會建立在:介紹自己產品多好用啦。講自己產品多屌啦。etc.etc.etc...

但事實上,多數成功的 Content Marketing 實作者是怎麼做的?

以 CodeClimate 來說。他們真正成名是這篇 *7 Patterns to Refactor Fat ActiveRecord Models

這篇幾乎所有程式 Refactor 書籍都會提到的經典文章。讓很多不知道怎樣將 code 寫得更好的人,有了大概的基本觀念。也對把 Code 寫得更乾淨這件事有了更高的認知。當他們在尋找「能協助將 Code 寫得更好的工具」時,就會想到 CodeClimate。

而 Groove。我是偶然在 HackerNews 上看到這篇轟動的部落格「把 SaaS 創業公司做到月營業額 10 萬美金」* A SaaS Startup’s Journey to $100,000 a Month 的文章後。開始訂閱他們的部落格。

而他們的部落格也常常會分享「週五創業家心得」、「客服做得更好」的例子。比如說我就很喜歡上個月的這篇文章 10 Tips for Sending Better Customer Service Emails

他們關於使用客服改善服務品質、促進銷售的文章總讓我獲益良多。因此,當我在尋找將客服做得更好的工具時,我第一個想到的就是 Groove。而且就算幾個服務攤開來比較。別家就算做得比較好,我也還是比較傾向使用 Groove。

See / Think 型觀眾才是內容行銷的 TA

所以其實內容行銷的寫作重點與 TA,不在於找「Do」層面的人。而是要找「See / Think」層面的人。

因為這兩個層面的人,不知道「投入這個領域的好處」「如何把這件事做到更好」。

如果你寫作的內容可以把它們的興致「燒起來」,將來他們要做購買決策的時候,第一個想的就是你。

Summary

所以你的內容行銷方向該怎麼做?

  • 不藏私,整理你知道的訣竅告訴大家
  • 把重點落在協助「想進入這個領域」的人,變得更強。

雖然這是很老套的觀點。但是你正在看的這篇文章,其實就是這個策略的示範。

 
about 2 months ago

RedPotion 是我最近在玩的一套 iOS App 開發框架。

大家應該了解 用 Ruby 開發的 Rails,在網站開發這一塊,有多強大。那麼白話的形容「redpotion」,你可以想像成它就是 RubyMotion 上的 Rails。

What is RubyMotion?

RubyMotion 是一套在 2012 年誕生的新語言,希望解決的事是「用 Ruby 寫出跨平台的 App」。跨平台的 App 當然包括 iOS App。

這在當時引起了一陣騷動,因為 Ruby 的語法比起 Objective-C 以及 JAVA 起來實在是好寫太多了。如果真的可以寫 Ruby,快速開發出 iOS 與 Android App,那會是一套很棒的事。

Why not RubyMotion?

當然,好日子沒有過的太長。Ruby Motion 更新非常快,母公司技術實力也雄厚。但在推展上卻有一點小阻力。

最大的問題有兩個:

  • Apple 在 2014 年中推出 Swift,把 iOS 的開發門檻大量的往下拉。
  • RubyMotion 的語法還是很囉唆,要寫一個 RubyMotion iOS App,純靠 Ruby 功力鐵定是不行的,你還是要在 Ruby 裡面寫出很多像 Objective-C 的 Code。

所以有人開玩笑說,如果要用 RubyMotion 加速開發,開發者的 Ruby 與 Objective-C 功力都要有一定才可以。否則還不如去學 Swift。

What is RedPotion?

RedPotion 是一家技術狂熱的公司 infinitered 在 2014 年底開始的專案。初衷有點像是 RubyMotion 懶人包,幫開發者準備好上手的 boilerplate (模板),內建許多厲害的 gem。逐漸把 RubyMotion 開發變成是更簡單的事。舉例來說,以下是兩個 app 的 app_delegate.rb ( app 起始檔案)

用 RedPotion 寫出來的程式碼,就比較像人可以維護的程式碼。也像是正常人「希望 RedMotion」可以長成的樣子。

RubyMotion 的 app_delegate.rb

motion_app/app_deligate.rb
class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    rootViewController = UIViewController.alloc.init
    rootViewController.title = 'my_new_app_2'
    rootViewController.view.backgroundColor = UIColor.whiteColor

    navigationController = UINavigationController.alloc.initWithRootViewController(rootViewController)

    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.rootViewController = navigationController
    @window.makeKeyAndVisible

    true
  end
end

RedPotion 的 app_delegate.rb 與 home_screen.rb

potion_app/app_deligate.rb
class AppDelegate < PM::Delegate
  include CDQ # Remove this if you aren't using CDQ


  status_bar true, animation: :fade

  ApplicationStylesheet.new(nil).application_setup

  def on_load(app, options)
    cdq.setup # Remove this if you aren't using CDQ

    open HomeScreen.new(nav_bar: true)
  end

  # Remove this if you are only supporting portrait

  def application(application, willChangeStatusBarOrientation: new_orientation, duration: duration)
    device.orientation = new_orientation
  end
end
app/screens/home_screen.rb
class HomeScreen < PM::Screen
  title "Your title here"
  stylesheet HomeScreenStylesheet

  def on_load
    set_nav_bar_button :left, system_item: :camera, action: :nav_left_button
    set_nav_bar_button :right, title: "Right", action: :nav_right_button

    @hello_world = append!(UILabel, :hello_world)
  end

  def nav_left_button
    mp 'Left button'
  end

  def nav_right_button
    mp 'Right button'
  end

def will_animate_rotate(orientation, duration)
    find.all.reapply_styles
  end
end

RedPotion 內建了什麼 gem 可以讓 RubyMotion 變得這麼好寫?

RedPotion 內建了幾個好用的 gem,讓 RubyMotion 寫起來更加容易,更像 Rails。

RMQ

一套 UI Library。支援

  • events and gestures
  • view factory
  • stylesheets
  • actions
  • position
  • selecting (querying views)
  • templates
  • traversal through view hierarchy (moving around the tree)
  • animations
  • validations
  • tagging
  • colors
  • fonts
  • image utilities
  • app
  • device

白話的說,就是用了 RMQ。可以用 jQuery 的方式 select UI 元件,做出你想要的視覺效果。然後也讓你的 View 可以用類似使用 stylesheets 的方式排版。

ProMotion

把 Screen 變得更好寫。提供了很多排版樣式,常見的內建排版形式都有提供:

  • Screens
  • Navigation Bars
  • Tab Bars
  • Table Screens
  • Grouped Tables
  • Searchable
  • Refreshable
  • SplitScreens
  • Map Screens
  • Web Screens

ProMotion 本身自己也是一套小型框架,所以有一卡車的 addons。比如說 roMotion-XLForm 就還蠻有名的。可以讓開發者很快地就作出一個表單。

比如說,這是我最近在開發登入畫面的程式碼:

sing_in_screen.rb
class SignInScreen < PM::XLFormScreen
  title "Sign In"
  stylesheet SignInScreenStylesheet

  form_options on_cancel: :cancel_form

  def cancel_form
    app.delegate.open_authenticated_root
  end

  def form_data
    [
      {
        cells: [
          {
            title:       'Email',
            name:        :email,
            type:        :email,
            placeholder: 'Enter your email',
            required:    true
          },
          {
            title:       'Password',
            name:        :password,
            type:        :password,
            placeholder: 'Enter your password',
            required:    true
          },
          {
            title: 'Sign In',
            name: :save,
            type: :button,
            on_click: -> (cell) {
              authenticate
            }
          }
        ]
      }
    ]
  end

  def authenticate
    Auth.sign_in(email: values["email"], password: values["password"]) do |response|
      if response.success?
        ApiClient.update_authorization_header(Auth.authorization_header)
        app.delegate.open_authenticated_root
      elsif response.object
        puts response.object
        app.alert "#{response.object['error']['message']} (#{response.object['error']['code']})"
      else
        app.alert "Sorry, there was an error. #{response.error.localizedDescription}"
      end
    end
  end
end

出來成果就會長這樣。

額外搭配使用: MotionKit

如果我要做的版面,超過 ProMotion 可以提供的部份(比如說我要做 Customized Cell),我還會用 MotionKit,直接拉版面。拉的方式很像是寫 HTML ..

CDQ (https://github.com/infinitered/cdq)

CDQ 可以讓開發者操作 Coredata 就像在 Rails 裡面操作 ActiveRecord 一樣。

author = Author.create(name: "Le Guin", publish_count: 150, first_published: 1970)
author.name # => "Le Guin"

author.publish_count # => 150

author.attributes # => { "name" => "Le Guin", "publish_count" => 150, "first_published" => 1970 }

author = Author.first
author.update(name: "Mark Twain", publish_count: 30, first_published: 1865)
cdq.save
author = Author.first
author.destroy
cdq.save

AFMotion(https://github.com/clayallsopp/afmotion)

AFNetoworking 的 Ruby 版。在接 API 時非常 friendly。介面非常像平常寫習慣的 Ruby http-client library。

其他

Gemfile 裡面還有一些選項是 Optional 沒有打開,諸如:

gem "ProMotion-XLForm"
gem "ProMotion-push", "~> 0.2" # Push Notifications
gem "ProMotion-map", "~> 0.3"  # PM::MapScreen
gem "ProMotion-iap" # PM In-app purchases
gem "ProMotion-menu" # PM Side menu
gem "motion-mastr" # Attributed strings: https://github.com/skellock/motion-mastr
gem 'motion-blitz' # Easy HUD with SVProgressHUD
gem "motion-juxtapose", "~> 0.1" # Screenshot acceptance comparison tool
gem "bubble-wrap"

Summary

總之,RedPotion 內建的這幾個核心的元件。就是圍繞著幾個 iOS 內建最常需要被解決的問題

  • Screen 如何拉
  • Layout 如何快速排版
  • 容易的「上色 Styling」
  • 容易的存取 Core Data
  • 很好處理 API 來的資料流

如果你想要享受在 Ruby 上如寫 Rails 般暢快的開發 App 感。歡迎再給 RedPotion 一個機會。

相關資源

 

宣傳連結

  1. 免費 Rails 學習手冊

    Buy Rails 101
  2. Intro to Growth Hack


    Growth GrowthSchool

    預購 8 折 中