Alex Liang

Swift Class 介紹

Class 宣告

Swift宣告class相當簡單,只要使用class這個關鍵字和class名稱

1
2
3
class TaxCalculator {

}

跟objective-c的宣告方式比較,不需要繼承NSObject或?生的類別。

Property

class的property則需要賦與初始值或使用init做初始化

1
2
3
4
5
6
7
8
9
10
11
class TaxCalculator {
let salary: Int
let taxRate: Double
var tax: Int

init(salary: Int, taxRate: Double) {
self.salary = salary
self.taxRate = taxRate
tax = Int(salary * taxRate)
}
}

由於init的參數和property名稱一樣,所以使用self來區別
另一個要注意的是,class可以有多個init函式,但使用的參數必須不同 (和c的overloading function一樣)

Methods

1
2
3
4
5
6
class TaxCalculator {
// 略
func calcSalary(tax: Int, taxRate: Double) -> Int {
return Int(tax / taxRate)
}
}

method的宣告需要加上回傳值的資料型態,若無回傳值可省略。

使用

1
2
let taxCalc = TaxCalculator(salary: 55000, taxRate: 0.15)
taxCalc.calcSalary(tax: 2000, taxRate: 0.15)

參考來源:
Swift 2 Tutorial

Swift 基本型態筆記

let vs. var

一開始接觸新語言時,首先會碰到各種資料型態的用法。
在c/c++ java這些較早的靜態語言,資料型態的宣告是一件需要斤斤計較的事
特別是資源有限的平台,變數如果佔太大的記憶體會是潛在的問題,更別說因裝入超過表示範圍的值產生的bug

Swift為變數宣告提供二種方式: let和var
前者為constant,也就是不可變動的值;後者則是一般的宣告
一般是建議能使用let就使用,complier會自動做最佳化的動作

如果想特別指定資料型態,也可用

1
2
let total : Int = 0  // 強制宣告total為Int
let taxRate = 0.13 // 一般的宣告方式

但如果沒特殊理由,使用預設的宣告方式以加強可讀性

Array and Dictionaries

1
2
3
let taxRate = [0.15, 0.18, 0.20]		// array
let emptyArray = [String]() // create an empty array with String type
var arr = [[Int]]() // 2 dimensional array of arrays of Ints

另一個資料型態為dictionary,這個在Python、Ruby都有類似的型態。
基本上就是一個key搭配value,可供建表及查循

1
var dict = [Int: Double]()					// dictionary

參考來源:
Swift 2 Tutorial

[Code School] Rails API Routing Note

假如web application需要提供API時,為了讓routing有效率及提升routing table的可讀性
原本的routes.rb如下

routes.rb
1
2
3
4
5
6
7
Rails.application.routes.draw do	
resources :posts # http://your-domain/posts

constraints subdomain: 'api' do
resources :contents # http://api.your-domain/contents
end
end

為了讓controller的目錄更有組織性,可以加上namespace

routes.rb
1
2
3
4
5
6
7
8
9
Rails.application.routes.draw do	
resources :posts

constraints subdomain: 'api' do
namespace :api do
resources :contents # http://api.your-domain/api/contents
end
end
end

雖然controller目錄變乾淨,但URL卻多出個api/contents。
加入path

routes.rb
1
2
3
4
5
6
7
8
9
Rails.application.routes.draw do	
resources :posts

constraints subdomain: 'api' do
namespace :api, path: '/' do
resources :contents # http://api.your-domain/contents
end
end
end

最後用一行搞定

routes.rb
1
2
3
4
5
6
7
Rails.application.routes.draw do	
resources :posts

namespace :api, path: '/', constraints: { subdomain: 'api' } do
resources :contents
end
end

Uploading image to AWS S3 with Paperclip

這二天將專案deploy到Heroku,卻因為paperclip和aws-sdk版本問題卡了好久
把解決過程記錄下來,以免未來再踩雷

一開始,照著Heroku 官方教學實作。
沒注意到文章表明此範例不支援aws-sdk 2之後的版本,以及沒指定paperclip的版本,導致圖片上傳失敗

事前準備

1. 必須要有AWS S3的帳號,設定方法可參考[這篇](http://alexliang-blog.logdown.com/posts/385515-aws-uses-s3-for-uploading-images)
2. 己安裝ImageMagick
3. 己安裝Heroku Toolbelt

設定

Gemfile
1
2
gem "paperclip", '~> 4.3.6'
gem 'aws-sdk', '< 2.0'

因為paperclip對新版的aws-sdk尚未支援,aws-sdk採用2.0以前版本;paperclip用穩定版本

config/environments/production.rb
1
2
3
4
5
6
7
8
config.paperclip_defaults = {
:storage => :s3,
:s3_credentials => {
:bucket => ENV['S3_BUCKET_NAME'],
:access_key_id => ENV['AWS_ACCESS_KEY_ID'],
:secret_access_key => ENV['AWS_SECRET_ACCESS_KEY']
}
}

這裡使用ENV表示機密資訊,你可以在application.yml設定其值(注意此yml檔要加入.gitignore)
但heroku不知道這些值,所以要另外設定

1
2
3
$ heroku config:set S3_BUCKET_NAME=your_bucket_name
$ heroku config:set AWS_ACCESS_KEY_ID=your_access_key_id
$ heroku config:set AWS_SECRET_ACCESS_KEY=your_secret_access_key

新增paperclip.rb檔在config/initializers下

config/initializers/paperclip.rb
1
2
3
4
5
if Rails.env.production?
Paperclip::Attachment.default_options[:url] = your_bucket_url
Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename'
Paperclip::Attachment.default_options[:s3_host_name] = 's3-us-west-1.amazonaws.com'
end

要注意:url的值為’bucket_name’+s3.amazonaws.com,例如bucket取名paperclip-test,則url為paperclip-test.s3.amazonaws.com
而:s3_host_name則需參考AWS文件做相對應的修改。

參考資料:
Stack Overflow討論文章
Paperclip github

Check list of Heroku deployment

在deploy到Heroku前,應該檢查的事:

開Branch

因為你永遠不知道deploy後有什麼狀況,修改branch也比較安全
事實上,在修改bug、開發沒做過的功能前,你都應該新增branch

Devise

如果有裝Devise,記得加上這段

config/initializers/devise.rb
1
config.secret_key = ENV["DEVISE_SECRET"]

1
$ heroku config:set DEVISE_SECRET=XXX

Database

因為Heroku不支援sqlite3,production改用postgresql

Gemfile
1
2
3
4
5
6
7
group :development, :test do
gem 'sqlite3'
end

group :production do
gem 'pg'
en

記得bundle install

參考資料:
Stack Overflow文章
設定production database

[Rails] 使用friendly_id增加URL可讀性

在預設的routing和URL中,每筆資料皆以id做查詢及顯示。
例如網站有個post model及controller,則第一則post id為1,其URL為/posts/1。
這對網站SEO及可讀性來說是很糟糕的做法。

如果要使用有意義的欄位(例如post title)作為URL的一部分,我們可以覆寫to_param並使用parameterize將id加上post title

app/models/post.rb
1
2
3
4
5
class Post < ActiveRecord::Base
def to_param
"#{id}-#{title}".parameterize
end
end

透過此方法,假如第一則post title為rails routing,則本來的//post/1會變成//post/1-rails-routing。
但這樣還是有id number在URL。想更進一步去除id,需要在post model加入名為slug的欄位

1
2
>> rails g migration AddSlugToPosts slug
>> rake db:migrate

修改post model

app/models/post.rb
1
2
3
4
5
6
7
8
9
10
11
class Post < ActiveRecord::Base
before_save :update_slug

def update_slug
self.slug = title.parameterize # 在儲存post前,先將title參數化存到slug
end

def to_param
slug
end
end

app/controllers/posts_controller.rb
1
2
3
4
5
6
7
class PostsController < ActionController	
... 略

def find_params
@post = Post.find_by_slug(params[:id]) # 由於id在to_param時己改為slug,找資料時得用slug
end
end

以上介紹的方法最大的問題是當title被更改後(例如rails routing 2),則URL會成為//posts/rails-routing-2
舊的網址將會失效,這對使用者來說會是非常不便的事。

因此介紹friendly_id這套gem,它能幫我們把URL變美觀,且省下很多力氣

Install

Gemfile
1
gem 'friendly_id', '~> 5.1.0' # Note: You MUST use 5.0.0 or greater for Rails 4.0+

接著bundle install以完成安裝

1
2
>> rails generate friendly_id
>> rake db:migrate

Model and Controller

app/models/post.rb
1
2
3
4
5
class Post < ActiveRecord::Base
extend FriendlyId
friendly_id :title, use: :slugged
... 略
end
app/controllers/posts_controller.rb
1
2
3
4
5
6
7
class PostsController < ActionController	
... 略

def find_params
@post = Post.friendly.find(params[:id])
end
end

將本來的find前面加上friendly這個method,才能找到資料。

參考來源:
Friendly ID介紹影片
friendly_id github

[Rails] 使用gmaps4rails增加Google Map

新專案需要將地址放在google map顯示,於是使用gmaps4rails實作
經過一陣鬼打牆的debug後才搞定這個功能,這裡記錄下來,也提醒自己別再犯錯。

Install gmaps4rails

Gemfile
1
2
gem 'gmaps4rails'
gem 'geocoder' # 協助轉助座標

然後bundle 安裝gem

Javascript Dependencies

這段code需要放在view裡面

1
2
3
<script src="//maps.google.com/maps/api/js?v=3.18&sensor=false&client=&key=&libraries=geometry&language=&hl=&region="></script> 
<script src="//google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.14/src/markerclusterer_packed.js"></script>
<script src='//google-maps-utility-library-v3.googlecode.com/svn/tags/infobox/1.1.9/src/infobox_packed.js' type='text/javascript'></script> <!-- only if you need custom infoboxes -->

接著在vendor/assets/javascripts底下新增underscore.js
並且將這段程式碼放入

Javascript Source Code

app/assets/javascripts/application.js
1
2
//= require underscore
//= require gmaps/google

Views

app/views/posts/show.html.erb
1
2
3
<div style='width: 800px;'>
<div id="map" style='width: 800px; height: 400px;'></div>
</div>

Javascript Code

在view底下加入javascript code,這段會顯示一個標記在經緯度原點的地圖

app/views/posts/show.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script type="text/javascript">
handler = Gmaps.build('Google');
handler.buildMap({ provider: {}, internal: {id: 'map'}}, function(){
markers = handler.addMarkers([
{
"lat": 0,
"lng": 0,
"picture": {
"url": "http://people.mozilla.com/~faaborg/files/shiretoko/firefoxIcon/firefox-32.png",
"width": 32,
"height": 32
},
"infowindow": "hello!"
}
]);
handler.bounds.extendWith(markers);
handler.fitMapToBounds();
});
</script>

Controller

假設我們有一個post controller和model,其中有個欄位是address
此欄位需要轉換為經緯度,並且在controller建立一個hash table做為google map標記的資料

app/controllers/posts_controller.rb
1
2
3
4
5
6
7
8
9
10
11
class PostsController < ApplicationController
...略
def show
@post = Post.find(params[:id])
@hash = Gmaps4rails.build_markers(@post) do |post, marker|
marker.lat post.latitude
marker.lng post.longitude
end
end

end

這裡要注意的是建立hash這段code要放在相對應的action,本來照著官網的示範做(它是放在index),一直都失敗
才發現是hash沒建出來

Model

app/models/post.rb
1
2
3
4
5
class Post < ActiveRecord::Base
validates :title, :address, presence: true
geocoded_by :address
after_validation :geocode
end

需要使用geocoded_by才能轉換至經緯度座楆

以上都設定好後,可以回頭修改script

Javascript Code

在view底下加入javascript code,這段會顯示一個標記在經緯度原點的地圖

app/views/posts/show.html.erb
1
2
3
4
5
6
7
8
9
10
<script type="text/javascript">
handler = Gmaps.build('Google');
handler.buildMap({ provider: {}, internal: {id: 'map'}}, function(){
markers = handler.addMarkers(<%=raw @hash.to_json %>); # 將hash table轉成json格式當標記
handler.bounds.extendWith(markers);
handler.fitMapToBounds();
handler.getMap().setZoom(15); # 預設zoom為15
}
);
</script>

到這裡就完成google map的顯示了,如果還需要其它功能(畫路徑、客製化圖層)請參考官方說明

參考來源:
Youtube教學影片
gmaps4rails github
geocoder github
“Change default zoom” from Stack Overflow

[Rails] KH Badminton Map計算聚會時間及使用helper整理views

上一篇搞定球聚時間的選單後,我們需要計算下次球聚的日期 (只顯示星期幾而沒有日期,不方便記日子)
藉由Ruby的Time class和rails的helper,可以將計算的過程藏在後面,而不弄亂前端的view

首先,因為球聚的時間是記錄星期幾,在model裡僅是個數字 (0~6表示週日到週六)
下次球聚的日期會根據使用者看到文章的當天計算
例如,預計週三聚會,使用者週四才看到文章。此時需顯示下次聚會日期 (也就是下週三)

Time Class

Ruby的Time class提供wday,可得知日期對應到星期幾,例如

1
2
3
4
2.2.0 :009 > t = Time.now
=> 2016-04-22 15:20:42 +0800
2.2.0 :010 > t.wday
=> 5

View

app/views/posts/show.html.erb
1
2
3
4
...略
<p>
時間:<%= next_meetup_date(@post.day.to_i) %>
</p>

Helper

day_offset是預計週幾聚會,利用Time.now.wday和它之間的關係算出下次聚會日期
next_meetup_date除了顯示日期,還加上星期幾

app/helper/posts_helper.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module PostsHelper
def calculate_date(day_offset)
meetup_day = day_offset
current_day = Time.now.wday
next_date = DateTime.now + (meetup_day - current_day).days

if meetup_day < current_day
next_date += 7.days # Next week
end
next_date.strftime('%Y/%m/%d')
end

def next_meetup_date(day_offset)
calculate_date(day_offset) + ' (' + I18n.t(:"date.day_names")[day_offset] + ')'
end
end

使用helper的好處是將來如果要改變顯示格式,只要改一個地方就好,其它的view也能使用。

參考來源:
Ruby Time文件
strftime 格式化時間的說明

[Rails] 使用i18n轉換中文詞彙

網站的頁面需要提供下拉式選單供使用者選擇星期幾
一開始使用Date::DAYNAMES取得一週的詞彙:

app/views/posts/_form.html.erb
1
2
3
4
5
6
<%= simple_form_for @post do |f| %>
...略
<p>
球聚時間:每週 <%= f.select :day, Date::DAYNAMES.zip((0..6).to_a) %>
</p>
<% end %>

然而預設為英文單字

1
=> [["Sunday", 0], ["Monday", 1], ["Tuesday", 2], ["Wednesday", 3], ["Thursday", 4], ["Friday", 5], ["Saturday", 6]]

我們想改成中文顯示,於是使用i18n轉換字詞

首先,在config/locales下新增一個zh-TW.yml檔,裡面建立中文的對應單字

config/locales/zh-TW.yml
1
2
3
4
5
6
7
8
9
10
"zh-TW":
date:
day_names:
- 星期日
- 星期一
- 星期二
- 星期三
- 星期四
- 星期五
- 星期六

要特別注意的是yml檔裡不能用tab來縮排,必需使用2個空白

接著設定config

config/application.rb
1
config.i18n.default_locale = 'zh-TW'

修改原來的view,即可轉換選單的英文字詞

app/views/posts/_form.html.erb
1
2
3
4
5
6
<%= simple_form_for @post do |f| %>
...略
<p>
球聚時間:每週 <%= f.select :day, t(:"date.day_names").zip((0..6).to_a) %>
</p>
<% end %>

參考來源:
“How to store and display a day of the week” from Stack Overflow
ihower介紹i118n
設定yml檔 from Stack Overflow

[Ruby] 1-D Array to Hash

在實作寄信功能時,需要把一維陣列轉成hash,於是思維還沒轉成ruby的我就寫下這段很初學者的code

原來的code,為了建立mail_list這個hash,花了6行程式碼

app/controllers/goals_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class GoalsController < ApplicationController
...略
def generate_json_params
mail_list = {}
count = 0
for email in @goal.shared_mails
mail_list[count.to_s] = email.mail_addr
count+=1
end

h = JSON.generate({ 'owner' => @goal.owner.name,
'goal' => @goal.title,
'complete_date' => @goal.complete_date,
'email' => mail_list })
end
end

使用Ruby內建的Hash[]可將陣列轉成Hash,再搭配with_index以index當key,一行搞定!

app/controllers/goals_controller.rb
1
2
3
4
5
6
7
8
9
10
class GoalsController < ApplicationController	
...略
def generate_json_params
mail_list = Hash[ @goal.shared_mails.map.with_index {|x, i| [i, x.mail_addr]}]
h = JSON.generate({ 'owner' => @goal.owner.name,
'goal' => @goal.title,
'complete_date' => @goal.complete_date,
'email' => mail_list })
end
end

(沒想到重構一個function居然可以寫3篇文章 XD)

參考來源:
ihower blog文章