Rails 5.1 导入 Vue

Vue太棒了。相比react的virtual dom大法,我们确确实实是在写html和js以及css。
Rails 5.1 加入了对各种JS前端框架的支持以及自带webpack, 所以,我们可以很方便就使用vue啦。

导入 vue

新建项目。

1
$ rails new myapp --webpacke=vue

我们会发现安装完成之后会多出来

1
2
3
4
app/javascript/packs                                                             
├── app.vue
├── application.js
└── hello_vue.js

在view中嵌入hello_vue,就可以显示定义在app.vue模版中的内容了。

1
2
3
# app/views/layouts/application.html.erb

<%= javascript_pack_tag 'hello_vue' %>

为了实现SPA,我们需要添加vue-router。

1
$ yarn add vue-router

然后建一些components。在这些components里面建一些模版。
大概像下面这个样子。

1
2
3
4
5
6
7
8
9
app/javascript/packs                                                             
├── app.vue
├── application.js
├── components
│   ├── home
│   │   └── index.vue
│   └── layout
│   └── footer.vue
└── hello_vue.js

最后,就是加上路由啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// app/javascript/packs/hello_vue.js

import Vue from 'vue/dist/vue.esm.js'
import VueRouter from 'vue-router'



import App from './app.vue'
import Footer from './components/layout/footer.vue'
import Home from './components/home/index.vue'


Vue.use(VueRouter)


Vue.component('my-footer', Footer)

const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/',
component: Home
}
]
})

document.addEventListener('DOMContentLoaded', () => {
new Vue({
el: '#app',
template: App,
router
})
})

注意点

  1. vue需要动态编译template的话,需要把importvue.esm而不是vue

  2. 不要混用require和import。这里只能用import。

以上。

将Rails Application 武装成 Progressive web Application

我们经常用websocket来实现后台消息推送。比如提醒用户新的留言,发帖,评论之类。
但是这种方式的局限性也很明显,就是当我们在浏览其他网页的时候,我们并不能知道在另一个网页上发生的这些通知。
为了在我们浏览其他网站的时候,也能在浏览器上收到我们在其他网站上的通知消息,就有了 webpush 这个东西。
有了webpush这种东西,我们就可以说,我们的web application是渐进式的为application了。

浏览器支持

主流的pc chrome 和firefox,android chrome。

什么,你不用这些浏览器? 食💩吧。

Rails的场合

下面我们就来给Rails Application添加webpush。

首先我们要生成和push service用于通信的公钥和私钥。

安装webpush这个gem。

1
2
3
4
5
6
7
# gem install webpush
# One-time, on the server
vapid_key = Webpush.generate_key

# Save these in your application server settings
vapid_key.public_key
vapid_key.private_key

把生成的public keyprivate key 作为环境变量保存。

接下来,将webpushserviceworker-rails 这两个gem添加到Gemfile中。

1
2
gem 'webpush'
gem 'serviceworker-rails'

bundle install,运行generator

1
rails g serviceworker:install

这样就会生成以下文件。

1
2
3
4
5
config/initializers/serviceworker.rb - 配置service worker的文件
app/assets/javascripts/serviceworker.js.erb - 一个带有一些例子的serviceworker文件
app/assets/javascripts/serviceworker-companion.js - 用来把你的serviceworker注册到浏览器中
app/assets/javascripts/manifest.json.erb - 上面提到的manifest
public/offline.html - 这个暂时我也没管是啥

因为要嵌入我们之前的公钥,所以先把app/assets/javascripts/serviceworker-companion.js 重命名成 app/assets/javascripts/serviceworker-companion.js.erb

添加如下内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
if ('serviceWorker' in navigator) {
console.log('Service Worker is supported');
var vapidPublicKey = new Uint8Array(<%= Base64.urlsafe_decode64(ENV["WEB_PUSH_VAPID_PUBLIC_KEY"]).bytes %>);
navigator.serviceWorker.register('/serviceworker.js')
.then(function(registration) {
console.log('Successfully registered!', ':^)', registration);
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidPublicKey
})
.then(function(subscription) {
$.ajax({
url: "/sessions/subscribe",
type: "post",
headers: { 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')},
data: {
subscription: subscription.toJSON()
},
success: function(data){
}
});
console.log('endpoint:', subscription.endpoint);
});
}).catch(function(error) {
console.log('Registration failed', ':^(', error);
});
}

这里我们注册我们的serviceworker,注册成功之后,我们得把浏览器返回给我们的关于push 服务器的一些信息持久化保存起来,利用我们保存的信息,我们就可以主动推送信息了。
这里先看一下serviceworker.js的内容。

打开 serviceworker.js添加如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
function onPush(event) {
var json = event.data ? event.data.json() : {"title" : "DEBUG TITLE", "body" : "DEBUG BODY"};
event.waitUntil(
self.registration.showNotification(json.title, {
body: json.body,
icon: json.icon,
data: {
target_url: json.target_url
}
})
)
}
self.addEventListener("push", onPush);

这样我们就会在收到push消息的时候在浏览器的右上角显示消息框。至于传过来的event里面的内容是什么,现在暂时无视好了。

接下来我们再看一下持久化subscription的部分。
也就是这个js的具体实现。

1
2
3
4
5
6
7
8
9
10

$.ajax({
url: "/sessions/subscribe",
type: "post",
headers: { 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')},
data: {
subscription: subscription.toJSON()
},
success: function(data){
}

注意在Rails中,ajax要带上csrf的header。

如何持久化因需求而异,这里简单地塞到数据库的表中而已。如果没有用户登陆的情况下,我们则塞到session里面。

1
2
3
4
5
6
7
8
9
10
11
12
class SessionsController < ApplicationController

def subscribe
subscription = JSON.dump(params[:subscription].permit!.to_hash)
if current_user
current_user.update(subscription: subscription)
end
session[:subscription] = subscription
head :ok
end

end

接下来就是激动人心的发送部分了。在发送之前我们得做一些准备工作,比如先把我们要发送的subscription从数据库或者session里面取出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def fetch_subscription(user)
if user && user.subscription
encoded_subscription = user.subscription
else
raise "Cannot create notification: no :subscription for user"
end
JSON.parse(encoded_subscription).with_indifferent_access
end

## message format
#message: {
# icon: 'https://example.com/images/demos/icon-512x512.png',
# title: title,
# body: body,
# target_url: target_url
#}
def webpush_params(user,message)
subscription_params = fetch_subscription(user)
message = message.to_json
endpoint = subscription_params[:endpoint]
p256dh = subscription_params.dig(:keys, :p256dh)
auth = subscription_params.dig(:keys, :auth)
vapid = {
subject: 'gyorou@tjjtds.me',
public_key: ENV['WEB_PUSH_VAPID_PUBLIC_KEY'],
private_key: ENV['WEB_PUSH_VAPID_PRIVATE_KEY']
}
{ message: message, endpoint: endpoint, p256dh: p256dh, auth: auth, vapid: vapid }
end

{ message: message, endpoint: endpoint, p256dh: p256dh, auth: auth, vapid: vapid } 就是我们要喂给
webpush的全部内容了。

我们调用Webpush.payload_send webpush_params(user, message)就可以完成一次发送。

实装时候的一些困惑

  1. 我们肿么知道要发送的目标的endpoint,p256dh这些东西?

这些东西其实来自订阅成功之后浏览器返回给我们的结果,我们不需要自己指定,只需要把结果持久化,等到要发送时候取出来,填入发送方法的相关参数就好了。

暂时以上。

是谁强制我SSL?

今天准备用omniauth和omniauth-oauth2 这两个东西自己写一下使用forum.qilian.jp账号作为第三方登陆的策略。
发现了一个蛋疼的问题,尽管我配置的callback url是类似https://localhost:3000/callback这种形式,返回的callback居然自动切换成了https。
在local开发的是时候,自然不需要https,当然最好也不需要https,所以必须得把这个问题解决。
那么到底是什么造成了callback成了https呢?

Rails 的 force_ssl 选项

查看配置,force_ssl是false,这个锅应该是别人的。

DoorKeeper 的 force_ssl_in_redirect_uri 选项

同样查看配置,发现 force_ssl_in_redirect_uri 也是false。

???

Nginx

最后在Nginx的配置里找到了这么一行。

1
proxy_redirect https:// https://

proxy_redirect 会在重定向的时候根据提供的规则修改Location header。
原本的配置本意是强制所有重定都指向https,其实有点画蛇添足了。因为这么一改,把原本指向非本站点的重定向也强制成https了。
注释掉这么一句,终于拜托了讨厌的重定向,看一下时间,已经是快接近黄昏的时候了。

Turbolinks Deivse 登录相关

我们想要在使用devise登录的时候实现用在form提交时候使用remote: true来提交表单。表单提交通过之后使用Turbolinks跳转到其他页面。
如果有验证错误则在当前页面显示验证错误。

为什么要这样做? 因为在Turbolinks-ios中无法用html方法提交表单。提交表单之后的跳转也必须由turbolinks来完成。

首先准备好需要render的js模版。

1
2
3
4
5
6
7
8
9
10
11
12
13
<% if resource && resource.errors.empty? %>
<% flash[:success] = "登录成功"; %>
Turbolinks.visit('<%=@redirect_to %>',{ action: "replace" });
<% else %>
$('#headFlash').html(`
<div class="alert alert-warning alert-dismissible fade in" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<span><%= flash[:alert] %></span>
</div>
`);
<% end %>

我们需要overwrite devise的sessionsController中的create方法。

1
2
3
4
5
6
7
8
9
class Users::SessionsController < Devise::SessionsController

def create
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
yield resource if block_given?
@redirect_to = after_sign_in_path_for(resource)
end
end

注意需要在router配置中标明我们进行了Devise::SessionsController的overwrite。

跑一下。登录成功,跳转。

故意输错密码,登录失败,但是直接server端返回了401,而并没有渲染create.js.erb模版。

这个是为什么?

重点在这里。warden.authenticate!(auth_options) warden的默认策略直接跳过下面的步骤直接返回给我们401了。我们需要告诉warden我们期待的是什么处理。

这里需要对oauth_options中的recall 进行overwrite。

注意到使用remote: true默认的request的format是:js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Users::SessionsController < Devise::SessionsController

respond_to :js
def create
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
yield resource if block_given?
@redirect_to = after_sign_in_path_for(resource)
end

def failure
render :create
end
protected
def auth_options
{ scope: resource_name, recall: "#{controller_path}#failure" }
end
end

故意输错一下试一试,特么为什么还是直接返回401而没有使用我们定义的failure方法?
去看一下devise initializer里面配置的说明。

1
2
3
4
5
6
7
8
9
# ==> Navigation configuration
# Lists the formats that should be treated as navigational. Formats like
# :html, should redirect to the sign in page when the user does not have
# access, but formats like :xml or :json, should return 401.
# If you have any extra navigational formats, like :iphone or :mobile, you
# should add them to the navigational formats lists.
#
# The "*/*" below is required to match Internet Explorer requests.
# config.navigational_formats = ['*/*', :html]

那么就是说只要config.navigational_formats = ['*/*', :html, :js]就ok了。

添加之后再试一下,还是不行。搜索一番。据说要添加

1
config.http_authenticatable_on_xhr = false

这特么是什么鬼。看一下devise default的failure app。

1
2
3
4
5
6
7
8
9
def respond
if http_auth?
http_auth
elsif warden_options[:recall]
recall
else
redirect
end
end

我们看到只有http_auth? 为false我们才会调用recall。

1
2
3
4
5
6
7
def http_auth?
if request.xhr?
Devise.http_authenticatable_on_xhr
else
!(request_format && is_navigational_format?)
end
end

可以看到我们只有设置Devise.http_authenticatable_on_xhr 为 false才使得验证失败时候的处理落到我们自己定义的recall中。

以上。

Turbolinks-ios中使用Devise进行登录

在Turbolinks-ios使用Devise的网页页面进行登录需要注意将表单变成ajax形式提交。

Rails部分

  • Rails的页面中,中使用ajax提交登录表单。

  • overwrite devise的session的create方法。结束时候渲染create.js.erb模版。

  • create.js.erb中使用Turbolinks.visit(after_url, {action: "replace"})方法转向登录后的页面。

Swift 部分

  • delegate页面中的turbolinks的链接时候对应的处理。
1
2
3
4
5
6
7
8
9
10
11
extension ViewController: SessionDelegate {
func session(_ session: Session, didProposeVisitToURL URL: URL, withAction action: Action) {
visit(URL: URL as NSURL, action: action)
}

func session(_ session: Session, didFailRequestForVisitable visitable: Visitable, withError error: NSError) {
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
}
  • visit函数中按传来的action进行navigationController stack的替换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func visit(URL: NSURL, action: Action) {
let visitable = WebViewController()
visitable.visitableURL = URL as URL!
switch action {
case .Advance:
pushViewController(visitable, animated: true)
break
case .Replace:
popViewController(animated: false)
pushViewController(visitable, animated: false)
break
default:
pushViewController(visitable, animated: true)
break
}
session.visit(visitable)
}

要特别注意的是处理replace的时候我们需要把登录页面从navigationController的历史里替换掉,否则我们登录之后点击返回上一页会返回登录页面的快照。
另外注意animated: false,这样用户就看不到我们替换的过程。

网文比纸上得来还浅

刚才解决了个困扰了很久的问题。从Ruby-china fork过来的forum的邮件总是无法发送。
ruby-china的邮件通过actionMailer的sidekiq adapter由sidekiq负责异步任务管理发送。
翻看后台的记录根本找不到任何error log,并且也有显示任务成功完成。那我的邮件被狗吃了么?

如果最近没人抱怨重置密码邮件无法发送我可能最近都会懒着不去找问题到底在哪里了。
谷歌了以下把原来的

1
bundle exec sidekiq -C config/sidekiq.yml

换成了

1
RAILS_ENV=production bundle exec sidekiq -C config/sidekiq.yml

尝试以下发送邮件,很顺利成功了。

总结来说,文档上没写,很多技术博客上也没带上production部署时候应该怎么搞,也只是摆个demo弄弄样子罢了。
更深一步,我们平时鼓捣很多东西,弄出个demo就沾沾自喜以为自己又学到了新的姿势,其实在实践面前也是不堪一击的。

以上。

Ghouse 2.0发布以及 wicked 这个gem 的一些坑

2月很快就过去了。过去的一周腰疼发作,起坐困难,挺着老腰站着工作了一周,这几天疼痛终于是渐渐减少了。
花粉症也是如期而至。眼角痒啊,鼻涕流啊,打喷嚏还怕闪着腰。

Ghouse 2.0 发布

前前后后做了一年的外包项目ghouse的第二版也终于发布了。这个项目后端包括管理后台和API基本由我独立完成,前端的Swift也参与改了不少。

要说过去的一年知道些什么know how,做过什么有意义的项目的话,大概都这个上面了吧。

gem wicked 的一些坑

用户通过简单邮箱密码注册后,收到确认注册邮件,用户电击邮件中的url,验证注册完毕,同时跳转到完善用户信息的页面。
用户信息分成两个部分,一个是基本信息,一个是学历信息。要用restful的方法实现该怎么写呢?
Rails 4 Application Development HOTSHOT
这本书推荐了一个叫wicked的gem。

还是从用户电击确认邮件的链接开始。我们overwrite Devise::ConfirmationsControllershowafter_confirmation_path_for两个方法。
show对应的的url是用户在邮件中电击的url。但是我们可以看到,用户点击这个url之后只能确认注册,而并不能直接登录。我们后面的操作需要用户已经登录,所以得先让用户登录。如注释,我们可以直接用sign_in方法将user_id 写到session中,这样用户就登录了。然后跳转到after_confirmation_path_for定义的path。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Users::ConfirmationsController < Devise::ConfirmationsController
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
yield resource if block_given?

if resource.errors.empty?
set_flash_message!(:notice, :confirmed)
sign_in(resource) # 跳转之前登录
respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) }
else
respond_with_navigational(resource.errors, status: :unprocessable_entity){ render :new }
end
end
private
def after_confirmation_path_for(resource_name, resource)
userinfos_path(id: :basic_info)
end
end

我们创建一个controller来实现userinfos_path(id: :basic_info)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class UserinfosController < ApplicationController
before_action :authenticate_user!
include Wicked::Wizard
steps :basic_info, :education
def show
@user = current_user
render_wizard
end
def update
@user = current_user
@user.update_attributes(user_params)
render_wizard @user
end
private
def finish_wizard_path
root_path
end
protected
def user_params
params.require(:user).permit(:user, :family_name, :family_name_kana,
:given_name, :given_name_kana, :gender, :birthday,
:major_category, :grade, :school_id, :education_level,
:department,:major, :grade_year, :education_category,
:japanese_level,:wechat,:phone,:job_category_id)
end

这个path会由UserinfosController#show来处理。我们定义的step分别是 :basic_info,:education

注意文档中说 wicked会默认将http parameter的id作为step的名称读取。所以这里我们明确使用了userinfos_path(id: :basic_info)。文档中类似userinfos_path(:basic_info)的用法会导致所有的step被跳过(生成的url中:baisc_info不是作为id)。所以文档是错误的。

我们创建两个template来按步骤对user进行更新。

1
2
3
4
5
6
7
8
9
# app/views/userinfos/basic_info.html.erb
<%= simple_form_for @user, url: wizard_path, method: :put do |f| %>
<%# my forms>
<% end %>

# app/views/userinfos/education.html.erb
<%= simple_form_for @user, url: wizard_path, method: :put do |f| %>
<%# my forms>
<% end %>

按照文档的说法我们需要让让form的helper知道我们用的是put方法提交表单,否则rails会找不到默认的post对应的update的方法。
这里比较坑的是,就算我们按照文档这么写,Rails还是会试图去寻找update方法,通过http parameters我们看到我们的action是update,虽然有个paramter_method=>'put',但是Rails依然告诉我们找不到update方法。 解决这个问题其实窑门显示得告诉路由我们的put对应的action是show(这样反而不restful),窑门还是老老实实就用post,然后去实现update方法。如上面的代码。经过纠结还是决定实现update方法。

update中调用render_wizard @user会试图保存当前user,如果保存成功则render下一个step,若保存失败则还是render本step。
这样以来代码就变得比价清晰了。

总的来说,wicked这个gem提出的思想和要解决的问题还是比较有意义的,但是这个文档写得实在是烂透了,只能亲自掉进坑里再想办法爬出来。

使用Devise的情况下在Doorkeeper验证跳转

当我们发起oauth2 request,从application跳转到第三方页面,然后填写用户名密码,登录,授权。
如果我们已经登录,那自动跳转回application。很多时候我们会搞混第三方页面登录后跳转和跳转回application这两件事情。

application这个跳转是Doorkeeper帮我们搞定的,其实这个过程可以看成是我们普通浏览一个url,这个url需要登录后才能浏览。
而处理这个url的Doorkeeper能够帮我们跳转回application。

Devise在我们使用用户名密码登录之后,一般我们会有照网站用户登录的行为跳转回主页的设定,但是,这样对于application就麻烦了。
我们需要的是在Devise验证登录之后,跳转到我们application发起的request。(注意这里不是跳转回application,我们还需要通过Doorkeeper进一步处理,由Doorkeeper跳转回Application)

这样逻辑就清晰很多了,做法就是把最初request的url写入session之后重载Devise的registrations和sessions等controller提供的方法。

1
2
3
4
5
6
# config/initializers/doorkeeper.rb
resource_owner_authenticator do
session[:return_to] = request.fullpath
current_user || redirect_to(new_user_registration_path)
#warden.authenticate!(scope: :user)
end
1
2
3
4
5
6
7
8
9
### app/controllers/users/registrations_controller.rb
def after_sign_up_path_for(resource)
session[:return_to] ? session[:return_to] : super(resource)
end

#app/controllers/users/sessions_controller.rb
def after_sign_in_path_for(resource)
session[:return_to] ? session[:return_to] : super(resource)
end

Embed image in ActionMailer and Recent interviews

Action Mailer embed image

如果按照普通写view的方法直接然后 <%= image_tag some_model.image_url %>
会因为渲染的是相对路径而在用户收到邮件的时候无法显示。
当然我们也可以硬编码host到相对路径前面,不过在staging, test环境下就会有些不自然。

解决方法是将图片打包成attachments 嵌入到邮件中。
具体做法

###

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# in action mailer
class SampleMailer < ApplicationMailer
def send_when_update(user)
# set attachment file
attachments["user_image"] =
File.read(Rails.root.join("public#{user.image.url}"))

# set mail header
mail to: user.email,
subject: 'new email!'
end
end

#in mail template

<%= image_tag attachments[@avatar_file].url %>

三次失败的面试经历

  • pwc合同会社 creative developer

有webtest + 面试。面试很水,很简单的自我介绍,特长,经历,没了。等人事通知下一次面试吧。前后不到20分钟。很卧槽的是面完之后跟我讲了句 我们这里很多人辞职去了你们公司。 我一得瑟忘了webtest没做完的事情,挂。

  • Line 什么j8职位忘了

因为之前说好了求人内推,然后朋友给我发了职位介绍,发现其中没有任何适合我的职位。随便扔了简历让人事帮我挑一个吧。

面试内容 笔试 30分钟,面试 1小时,面试内容是根据笔试内容来的。笔试内容的话其实刷点题,好好准备一下的话应该非常轻松。但是本人秉着考察一下这家公司气氛到底如何心态去面试,何况平时就对面试考算法深恶痛绝所以基本就是裸着上场了。

笔试题首先是问卷,你用过那哪些框架啊,哪些语言啊,哪些特么CI,CD啊,哪些特么package管理工具啊。不过候选项目全是Java栈的东西,特么gg。

其他题目,进程线程区别,排序算法复杂度,两个栈实现一个队列,blocking和non-blocking, 还有个啥j8算法题太长描述不清楚,反正我也是跳过,最后问一个预计有10k qps的echo server如何构建的问题。

综合来说除了算法题实在是年代太久没去仔细准备其他关于构架和OS的问题基本都是得心应手,所以看看面试怎么说吧。

面试官两个人,没自我介绍就开始了以至于我后来也不知道特么这两人是谁。基本就是让我解释笔试的题目。老实说让我上黑板写算法那是算了。这种东西一半工程很少用到,不过凭着我特么大学时代专业前三的的成绩我觉得我至少还记得一点,可惜这两位面试官貌似不是很愿意听我的解释或者觉得我说的比较难懂,反正就是脸一歪表示我听不懂你在说什么的表情。。。

OS和构架的问题,好像说的太多了,其实这两个人也不是很懂。所以还是一脸表示听不懂我在说什么的表情。这个时候其实心里已经卧槽了。

最后面完之前问我我的服务主要就是Java,你没有java经验你觉得有问题吗? 废话问题当然很大,但是语言这种东西特么学的多了自然上手快,经验? 不好意思我没有,但是快速上手的能力我是有的。

不多说了反正也是挂了。综合来讲面试题目一般,面试官略傻逼。没有适合我的职位。

  • 迅销 全栈工程师

猎头介绍的公司,特么也不知道猎头能拿多少钱说了超多这个公司的好话。不过戏剧性的是在我等待这个公司终面结果的时候这个猎头辞职跑路了。
比较伤感的是我都做好了七点钟跑去上班的准备结果最后一面落了。真是以后特么我要往死里黑这家公司。另外这家公司的屌丝产品我以后是绝对不会买的。

1面技术面,英语。JS的问题偏多,问题类型偏广但深度不深。比如Node的Event loop, server side rendering,node的callback hell如何解除。当前流行的Web框架,现在学习中的Elixir的特点,当场写个test case这种。

2面IT部长面,忘了问了啥了。台湾年末吃喝玩乐Skype面的试。就问了些你当前有哪些想做的service之类。

3面CIO面试。问问家常,问问情怀。最后切换成英语问我你觉得现在你26,那么30岁你在干什么。我觉得我就是死在这个问题上了,不假思索说了自己应该是在一个start up当CTO这种屌话。

等了一个星期结果反正就是挂了。

以下是总结,这三个公司其实都没有适合我的职位。为了吃个饭,为了看看人们传说中的优良公司,因为被猎头洗脑过度所以总共浪费了半天带薪休假的事情去经历了这些面试。
总而言之还是应该抱着去找自己想做的事情的心态,而不是觉得我能做,所以让我来吧这种。求职和同异性交往一样,很多时候你可能千方百计想要改变自己的初衷去迎合对方,到头来还是暴露了彼此的不合适。我们真正需要去寻找的,还是那个适合自己的人,适合自己的岗位,而不是勉强自己去证明我才是最合适的。

以上。

sqlite3 migrate to postgresql && swift parse response time string from api

Rails migrate sqlite3 into postgresql

reference
尝试了pgloader, 遇到的问题很多。一筹莫展了半个下午。
尝试sequel,意外地顺利。

1
sequel -C sqlite://db/production.sqlite3 postgres://user@localhost/db

得到的教训是从刚一开始就应该使用postgresql,其次sqlite3中并不严格检查表项的类型,
导致以前定义成timestamps的表项直接被当string存储下来,给时间的转换统一带来了不少麻烦。

将API传来的关于时间的string转换成Swift的Date

1
2
3
4
5
6
7
8
9
10
// str =  "2016-05-31T08:18:37.969Z"
// string.asDate(with: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
extension String {
func asDate(with dateFormat: String) -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(identifier: "UTC")
dateFormatter.dateFormat = dateFormat
return dateFormatter.date(from: self)
}
}