Hegwin.Me

寓形宇内复几时?曷不委心任去留?胡为乎遑遑欲何之?

Rails优化SQL可能用到的工具

Rails查询数据慢?想找到性能瓶颈在哪里?

目前我个人是用到了以下几个工具:

  1. New Relic 老牌的性能检测工具,与Rails集成之后,在Database监控中能显示最慢的Query之类的,不过这需要New Relic至少订购了Essentials级别的计划。

  2. Gem: active_record_query_trace 你可以在log中看到每个SQL是来自于哪一条代码。

    使用场景: 用时候会发现尽管在controller中已经查询了/预加载数据,但在渲染view的时候,还是出现了SQL查询,这个工具可以帮你追踪到是view中哪一行代码造成了数据库查询。不过这个工具其实并不区分SQL是在controller还是view里查询,只要进行了SQL操作,都会显示调用栈的前N个记录(N默认为5)。

  3. Gem: bullet 这个gem可以用来提醒你,项目中发生了N+1查询,在Sinatra中也可以使用。

为PostgresQL安装扩展程序

如果在安装Postgres时,选自下载源码编译安装,而make时又没有make world,就会导致的pg最终没有类似pg_stat_statements的扩展功能。那么,这篇文章将会讲述如何在编译安装后,为pg安装扩展。

让我们以 pg_stat_statements 为例,前提是我们希望人们没有删除当初用于编译pg的目录。

首先,看一下pg的源码目录,可以看到有一个名为 contrib的目录。

$ ls
aclocal.m4  config  config.log  config.status  configure  configure.in  contrib  COPYRIGHT  doc  GNUmakefile  GNUmakefile.in  HISTORY  INSTALL  Makefile  README  src

在这个目录中,我们可以看到扩展包:

$ cd contrib
$ ls

都在这里:

adminpack    btree_gist        dblink        fuzzystrmatch    intarray        Makefile        pgcrypto        pg_stat_statements  README  start-scripts  tsm_system_rows  xml2

auth_delay    chkpass            dict_int      hstore          isn            oid2name        pg_freespacemap  pgstattuple        seg      tablefunc      tsm_system_time

auto_explain  citext            dict_xsyn      hstore_plperl    lo              pageinspect    pg_prewarm      pg_trgm            sepgsql  tcn            unaccent

bloom        contrib-global.mk  earthdistance  hstore_plpython  ltree          passwordcheck  pgrowlocks      pg_visibility      spi      test_decoding  uuid-ossp

btree_gin    cube              file_fdw      intagg          ltree_plpython  pg_buffercache  pg_standby      postgres_fdw        sslinfo  tsearch2      vacuumlo

假设我们要安装pg_stat_statements,步骤相当简单,其他的扩展包也是类似的安装方式:

$ cd pg_stat_statements
$ make
$ sudo make install

官方文档在这里,一开始没有写明在哪里执行make && make install 所以比较confusing。

https://www.postgresql.org/docs/9.4/static/contrib.html

修改Repo地址后Capistrano部署问题

其实有点闹妖的成分, 我们用Capistrano部署项目,最近把项目仓库从BitBucket迁移到了Github上,本以为只要把 config/deploy.rbset :repo_url改一下就好,但事实并非如此。

用Cap执行部署时,看到Log里还是从BitBucket的repo里拿的代码,所有迁移到GIhub之后的改动都没有包含进去。

在网上搜了下,需要自己ssh登陆到服务器上去修改repo地址,唉,真是自己作妖。

打开 /var/www/my-project/repo/config

[core]
        repositoryformatversion = 0
        filemode = true
        bare = true
[remote "origin"]
        url = git@bitbucket.com:hegwin/my-project.git
        fetch = +refs/*:refs/*
        mirror = true

将其中的url 改成新的repo的URL,然后在本地重新部署即可。

另,不要忘记重新添加 Deploy Key。

Ref: https://coderwall.com/p/4k1lja/fixing-capistrano-3-deployments-after-a-repository-change

在Rails中实现HTTP Long Polling

关于ActionController::Live

通过使用 ActionController::Live,我们可以实现实时的Push通信。官方文档在这里,但总觉得学习起来,这个文档太简略。

这里面主要是利用了这几个技术:

  • 当HTTP/1.1 的Header指定为 Transfer-Encoding: chunked 时,数据将会被切分为多块、然后持续地被返回

  • ActionController::Live将Response设定为 Transfer-Encoding: chunked 返回数据

  • SSE(Server-Sent Events) 即浏览器向服务器发送一个HTTP请求,然后服务器不断单向地向浏览器推送“信息”(message)。这种信息在格式上很简单,就是 “信息”加上前缀“data: ”,然后以“\n\n”结尾

实现一个Push通知

我们来构造一个PusherController:

class PusherController < ApplicationController
  # 引入模块
  include ActionController::Live

  def stream
    # 设定Responst的Content-Type
    response.headers['Content-Type'] = 'text/event-stream'
    10.times do |i|
      # 写入数据,数据的写法是基于上述SSE的格式要求
      response.stream.write "event: message\n"
      response.stream.write "data: Hello world ##{i+1}\n\n"
      sleep 1
    end

    response.stream.write "event: done\n"
    response.stream.write "data: Done\n\n"
  rescue IOError
    # client disconneted
  ensure
    # 最后,切断client的连接
    response.stream.close
  end
end

配置路由:

  get 'stream' => 'pusher#stream'

此外,由于Rails在开发环境下默认的WEBrick,其并非多线程的Server,我们需要使用一个支持多线程的app server,在Gemfile中加入Puma,然后 bundle install

gem 'puma'

rails s启动app之后,在命令行使用curl就能看到数据被一行行传过来了:

curl -i http://localhost:3000/stream

服务端这边已经没有问题了,那前台应该如何使用呢?我们来做一个简单的例子

利用EventSource来控制View显示

我们在PusherController里添加一个名为index的action:

class PusherController < ApplicationController
  include ActionController::Live

  def index
  end

  def stream
    response.headers['Content-Type'] = 'text/event-stream'
    10.times do |i|
      response.stream.write "event: message\n"
      response.stream.write "data: Hello world ##{i+1}\n\n"
      sleep 1
    end

    response.stream.write "event: done\n"
    response.stream.write "data: Done\n\n"
  ensure
    response.stream.close
  end
end

再配置路由:

  get 'stream' => 'pusher#stream'
  get 'pusher' => 'pusher#index'

新建一个view: app/views/pusher/index.html.erb

<h1>Pusher#index</h1>

<div id="result">
</div>

<%= javascript_tag do %>
  $(function() {
    var eventSource = new EventSource("/stream");

    eventSource.addEventListener("message", function(event) {
      $("#result").text(event.data);
    });
    eventSource.addEventListener("done", function(event) {
      this.close();
      $("#result").text(event.data);
    });
  });
<% end %>

注:这里的message, done是在controller中写入stream时,自己定义的类型。

此时,用浏览器访问 http://localhost:3000/pusher 就能看到从服务端推送过来的数据啦,效果如图:

Refs:

  1. SSE:服务器发送事件
  2. ActionController::LiveとServer-Sent Events で地図上にじわじわ表示する

Git 找回reset --hard的代码及删除的分支

人总是会犯错的。

还记得第一次使用 git reset --hard + git push -f 是在2013年初。那时候我图省事,在staging服务器上修改了点代码,就直接push到github上去了,本以为只是一个小提交,结果push了一堆无用的代码上去……怎么办呢,这时候有前辈出来告诉我,即使是push到服务器的代码依然是可以修改的,同时告诉其他同事暂时不要push代码,最终用 reset --hard 和 force 解决了这次问题。

终于,这么多年过去之后,我似乎也成了“前辈”(然而水平也并没有太多提高)。

今天一个同事忽然说把代码弄没了—— reset hard之后, force push到github上了,很慌张的样子……哈哈,我之前那次也是各种忐忑不安,想想也是记忆犹新。

回到开头那句,人总是会犯错的,在改错的过程中也会犯错,虽然两次犯错叠加之后的概率很小,但还是有可能发生的。那么reset --hard之后还能找回代码吗,当然是可以的。 Git never deletes a commit. 这时候需要reflog出场了。

使用

git reflog

或者

git log --walk-reflogs

你能看到你之前的改动都还在,这时候你该心安了——即使reset --hard也不会丢失,这之后的套路应该熟悉了吧,找到你需要的commit,然后reset过去。

同理,用 -D 删除的分支也是可以找回来的。用reflog查看记录,找回分支删除前的commit的hash,比如4c84318,然后这样:

git branch <branch-name> 4c84318

人生第一个全程马拉松

全程马拉松,42.195公里,在两年前我从来不敢想象我能完成这个距离。

然而,就上个周末,2016年12月11日,我在广州完成了人生第一个全程马拉松。净成绩5小时12分,不是很好的成绩(主要还是由于训练太少,赛前只拉过一个3小时的LSD),但毕竟完成了!

跑过终点时,我是累的一塌糊涂,已经没有任何力气去做拉伸运动,慢慢走了十来分钟之后,找了个阴凉的地方坐下,大概一刻钟之后才恢复精神,想着应该要自拍一个什么的。这才拿出奖牌,胡乱拍了个照片。

微软考试70-480知识点整理

HTML5

  • New semantic tags
    • <section>
    • <article>
    • <header>
    • <footer>
    • <aside>
    • <nav>
    • <hgroup> Deprecated in HTML 5.1
    • <figure> & <figcaption>
    • <main>
    • <time>
  • Web Worker 及 Workers通信
  • WebSockets
  • localStorage
  • SVG &/vs. Canvas
  • Video
  • navigator.geolocation
  • navigator.online
  • addEventListener
  • attachEvent
  • appendChild
  • new <input> types
    • email
    • search
    • url
    • range
    • date
    • tel
    • number
    • month
    • week
    • time
    • datetime-local
    • color

Javascript

  • throw Error
  • catch Error
  • Inheritance
  • instanceof
  • isPrototypeOf
  • function #call() #apply()
  • isNaN()
  • parseInt()
  • JSON.stringify()

jQuery

  • jQuery.ajax()
  • jQuery.on()
  • header选择器

CSS3

  • position: relavite | abosolute
  • box-shadow
  • text-shadow
  • box-sizing: padding-box | border-box
  • color: rgba(), hsla()
  • Gradien: linear-gradient() & radial-gradient()
  • @font-face
  • transform:
    • translate(x, y), translateX(), translateY
    • rotate(45deg)
    • scale(x, y), scaleX(), scaleY()
    • skewX(10deg), skewY()
  • transition:
    • transition-property
    • transition-duration
    • transition-timing-function
    • transition-delay
  • hyphens
  • text-transform: captialize | uppercase | lowercase
  • > 选择器
  • Propterty Selector
  • -ms-grid, -ms-grid-rows, -ms-flex
  • 3D transform
  • user/user agent/author CSS and !important

在Ubuntu16.04上安装日语输入法

闲话:我个人认为,目前来说,如果新学一门外语,安装PC/手机/Mac上对应的输入法是一件很有必要的事情。通常情况下,我们的外语老师,一般只会教我们语言本身,而不太会去教你如何安装输入法,至于日语输入法的安转,都是同学之间相互讨论才得以完成的。就日语来说,我觉得输入法在不同的平台上可以这么安装:安卓手机上可以去装谷歌的日语输入法,这其中Sony手机是自带日语输入法也挺好用,故Sony可以不必特意安装;而苹果的话,iPhone/iPad都自带各种语言的输入法,在“通用->键盘”里添加就好,说起来iOS自带的日语输入法还是不错的;Windows上,同安卓手机,谷歌日语输入法;而Ubuntu的话,就是我这篇想要分享的。

我比较喜欢的输入法是ibus-mozc,可以认为是谷歌日语输入法的开源版本吧,有许多emoji(这大概是日语输入法的一大特征了吧 TAT)。

这两个配置是安装任何语言都需要用到的:*Language Support* 和 Text Entry

首先在Language Support里Install Language,把Japanese勾上。

而后系统会检测和安装一些文件,然后我们需要做的就是去安装ibus-mozc了,安装方法是在命令行:

$ sudo aptitude install ibus-mozc

然后我发现了一个神奇的事情,不知道是不是Ubuntu16.04在安转语言支持的时候默认安装了这个输入法,所以我看到的是这样的结果:

$ sudo aptitude install ibus-mozc
[sudo] password for hegwin: 
ibus-mozc is already installed at the requested version (2.17.2116.102+gitfd0f5b34+dfsg-1ubuntu1.1)
ibus-mozc is already installed at the requested version (2.17.2116.102+gitfd0f5b34+dfsg-1ubuntu1.1)
No packages will be installed, upgraded, or removed.
0 packages upgraded, 0 newly installed, 0 to remove and 7 not upgraded.
Need to get 0 B of archives. After unpacking 0 B will be used.

这时候打开Text Entry设置,并没有看到Mozc输入法,但其实已经安装好了。

你需要log out一下,或者干脆重启下,再进入Text Entry就能看到Mozc输入法了,加上就好。

16.04下默认的切换语言的快捷键是Super+Space,我用不习惯,还是使用了Ctrl+Space。

之后就能愉快地使用日语输入法啦~

测试React应用程序

Main things:

  • mocha - Test Frame
  • chai - provides TDD/BDD syntax (should, expect ) for testing
  • enzyme - procides component renderer such as shallow and mount, and DOM manipulation and traversal
  • sinon - provides mocks and spies

学习ES2015 Part 4

Object 新特性

Shorthand语法

ES2015有这么一个语法糖,新生成一个Object时,当键名与变量名相同时,可以有一个简略的写法。具体怎么写,看下面的例子。

过去,我们需要这样写:

function buildUser(first, last) {
  var fullName = first + ' ' + last;
  return { first: first, last: last, fullName: fullName };
}

上面这个function中return的部分显得非常累赘,我们现在可以这样写:

function buildUser(first, last) {
  let fullName = `${first} ${last}`;
  return { first, last, fullName };
}

这个例子中,我们也结合了前面几部分说到的内容:

  1. 使用let代替var进行变量声明

  2. 使用Template String代替了字符串拼接

  3. 使用了 Object 的 Shorthand 写法

当Object的属性是一个方法时,我们也可以使用类似的语法,比如我们这样写的function (Shorthand语法和非Shorthand语法是可以混用的):

function buildUser(first, last) {
  let fullName = `${first} ${last}`;
  const ACTIVE_POST_COUNT = 10;

  return { 
    first,
    last,
    fullName,
    isActive: function() {
      return postCount >= ACITVE_POST_COUNT;
    }
  };
}

我们可以省略 function 关键字,直接用方法名:

function buildUser(first, last) {
  let fullName = `${first} ${last}`;
  const ACTIVE_POST_COUNT = 10;

  return { 
    first,
    last,
    fullName,
    isActive() {
      return postCount >= ACITVE_POST_COUNT;
    }
  };
}

Object Destructing

这个特性和Array的Destructing非常类似,当我们只需要一个Object的部分属性时,不再需要一行次次去取。在ES5中:

let user = buildUser('Hegwin', 'Wang');

let first = user.first;
let last = user.last;
let fullName = user.fullName;

在ES2015中,则可以这样:

let user = buildUser('Hegwin', 'Wang');

let { first, last, fullName } = user;

只要需要赋值的变量名与Object的属性名相同,我们就可以这么操作。同时,我们也可以只取部分的属性:

let user = buildUser('Hegwin', 'Wang');

let { last, fullName } = user;

使用 Object.assign

如果我们需要合并多个Object,例如在function中,我们需要用参数中的值覆盖默认的配置,在这种场景下,我们可能会使用 jQuery.extend( target [, object1 ] [, objectN ] )这样的第三方提供的方法,而现在 Object.assign 就实现了这样的功能。举个例子就是这样:

function countdownTimer(target, timeLeft,options={}) {
  let defaults = {
    // default options here ...
  };

  let settings = Object.assign({}, defaults, options);

  // rest codes ...
}

注: 上面这个 Object.assign 的第一个参数,我们传了一个空的 Object,这是为了防止 defaults被覆盖而无法继续使用。

学习ES2015 Part 3

Array 新特性

Array Destructing

感觉这也是从Ruby或者类似的语言学习过来的方法,在ES5,我们没有办法直接把数组的各个值一次性赋给多个变量,而现在我们可以了,假设我们有这么一个数组:

let users = [ 'Hegwin', 'Jason', 'Ken' ];

如果我们想直接把users的三个元素依次复制给三个变量,那我们就可以这样:

let [ a, b, c ] = users;
// a => "Hegwin", b => "Jason", c => "Ken"

同时,我们也可以忽略掉数组中的部分元素:

let [ a, , b ] = users;
// a => "Hegwin", b => "Ken"

我们也可以使用延展方法(spread):

let [ first, ...rest ] = users;
// first => "Hegwin"
// rest => ['Jason', 'Ken' ]

这三个点表示把数组中剩余的部分,以数组的形式复制给rest变量。

for ... of 循环

还是这个数组 let names = [ 'Hegwin', 'Jason', 'Ken' ] ,如果我们需要循环依次取出数组的元素,在ES5中,我们需要使用for ... in 循环,就像这样:

for (let index in names) {
  console.log(names[index]);
}

而在ES2015中,我们可以利用for ... of循环直接在循环时取出元素本身,而不再需要通过下标去取:

for (let name of names) {
  console.log(name)
}

Array的find方法

假设我们有这么一个数组,里面有三个对象:

let users = [
  { login: 'Ken', admin: false },
  { login: 'Joe', admin: true },
  { login: 'May', admin: ture },
]

现在我们需要找出第一个admin为true的user,过去我们需要使用循环,要么是js原生的,要么是jQuery或者lodash提供的一些API,而在ES2015中,我们可以使用新增的find方法:

let admin = users.admin((user) => {
  return user.admin;
})

这里用到了 => 语法,我们也可以写的更简单:

let admin = users.find( user => user.admin );

新的数据结构 Map

Map 是什么?

Map简单说就是映射,是一种键值对存储数据的结构,那么问题来了,Map和Object有什么区别,各自有什么应用场景?简单说来是这样的:

  1. Object 会强制的将key转换为string,无论这个key原先是什么;

  2. 如果你能预先知道你的key,那么用 Object,如果key的值只有在运行时才能得到,那么则用Map。

对上面简单举个例子说明下:

let array = [1, 2, 3];
let object = {};
object[array] = "Myth";
console.log(object);

let map = new Map();
map.set(array, "Answer")
console.log(map);

输出:

Object {1,2,3: "Myth"}
Map {[1, 2, 3] => "Answer"}

比较一下就可以看到,在Object中,我们尝试用一个数组去做key,结果得到的结果,却是使用了'1,2,3'这个字符串去做的key,而Map中则直接用的原来的结构。

基本操作

说起来的Map的基本操作无非就是:赋值、取值、删除,以及在某些时候我们需要遍历一个Map对象。

前三个方法都很简单,即 set(key, value)get(key)delete(key),而Map的遍历我们可以用for ... of循环:

let map = new Map();
map.set('user', 'Hegwin');
map.set('topic', 'ES2015');
map.set('replies', ['So cool!', 'Wanna have a try']);

for (let [key, value] of map) {
  console.log(`${key}: ${value}`);
}

输出:

user: Hegwin
topic: ES2015
replies: So cool!,Wanna have a try

WeakMap

WeakMap和Map的思想比较相似,但是又有不同:

  1. WeakMap的key必须是一个Object对象

  2. 不能使用for...of 循环需遍历一个WeakMap对象

  3. 在内存上更加高效,Map的key对Object的引用是一个弱引用,在垃圾回收时,如果一个Object仅仅被WeakMap引用,那么这个Object是可以回收的。

注:WeakMap名字的由来,也是由于“弱引用”这个缘故。

新的数据结构 Set

Set顾名思义,即“集合”,它的诞生背景是Array中允许重复的元素,而我们还需要集合这种数据结构,保证其中的元素是唯一的。

Set 的基本方法

  • add(value) : 添加
  • delete(value) : 删除
  • has(value) : 返回true/false,表示Set是否包含这个值
  • clear() : 清除所有元素

举例来说就是这样:

let tags = new Set();

tags.add("JavaScript Programming");
tags.add({ version: "2015"});
tags.add("web");
tags.add("web");

在最后我们尝试两次添加值为"web"的元素,但实际上第二次添加并没有生效:

console.log(tags.size)  // => 3

Set 的遍历

对于Set我们可以采用for...of循环:

for (let tag of tags) {
  console.log(tag);
}

也可以使用类似数组的spread方法:

let [a, b, c] = tags;

WeakSet

WeakSet写在Set之后,其关系和 WeakMap之余Map非常相似。

  1. WeakSet的值不能读取,也不能通过for ... of 循环取出

  2. 只允许添加Object作为元素

  3. 基于以上,常用的方法只剩下#has#delete

  4. 同样是优化了GC时的性能,同WeakMap

学习ES2015 Part 2

String

ES2015中加入了Template String的概念,当我们想要把变量值拼接到字符串中间时,终于不需要不停地的使用+了,这也是我觉得可以欢呼的地方。

具体是这样操作的:JS里普通那个的字符串依旧是用单引号或者双引号包围,但是Template String则用反引号包围(感觉在Markdown里打个反引号真要命)。

过去:

let fullName = firstName + ' '  + lastName;

现在:

let fullName = `${firstName} ${lastName}`

其中我们使用${}将变量包围起来(怎么总觉得包围这个词怪怪的)。

使用这种这种方式,我们也可以构造多行的长字符串:

let userName = 'Same';
let admin = { fullName: 'Hegwin Wang' }

let aLongText = `Hi ${userName}
This is a very
loooooooooog text.

Regards,
${admin.full_name}
`;

Class

Class 语法

ES2015增加了class的语法,不过据我所知,这只是增加了一个类似语法糖的东西,JS实现OO的本质还是和原先一样。上代码,举个例子:

class TagWidget {
  constructor(name,url) {
    this.url = url;
    //...
  }

  render() {
    let link = this._buildLink(this.url)
    // ...
  }

  _buildLink() {
    // ...
  }
}

在这一段代码里,我们用class关键字去声明一个类,然后用大括号包围起来,里面有若干个方法,方法之间不需要用逗号或者分号分隔。

  1. 每当我们用new去新建一个实例时,construcor中的代码都会被执行,类似ruby的initialize

  2. this访问的内容,是实例属性,或者实例方法。

  3. 以下划线开头的方法,比如例子中的_buildLink,是私有方法。

对于上面声明好的类,我们可以这样调用:

let tagWidget = new TagWidget(name, url);
tagWidget.render();

类继承

假设我们有Widget这么一个类,打算用作所有widget的父类:

class Widget {
  constructor() {
    this.bassCSS = 'widget';
  }

  parse(value) {
    // ...
  }
}

新的语法中,允许使用extends用来指定父类,假设TagWidget现在继承于Widget类:

class TagWidget extends Widget {
  constructor(name, url) {
    super();
    // ...
  }

  parse() {
    let parseName = super.parse(this.name);
    return `Tag: ${parsedName}`;
  }

  render() {
    // ...
  }
}

这其中super可以用来调用父类的方法; 另外TagWidget中parse的返回值,也是使用上面说的Template String的方法。

学习ES2015 Part 1

变量声明

Using let

  • 终于不再有var神奇的hoisting了,可以声明块级的变量了 (欢呼)

  • 解决for中变量泄漏的问题

  • 同一block中,let声明的变量可以重新赋值,但再次声明同名的变量时会抛出异常 TypeError

Using const

  • 声明一个read-only的变量,生命时必须赋值

  • 给const变量赋值时,一般会报错,但有的浏览器会静默处理

  • 可以避免那些magic number

关于Functions

参数默认值

function loadProfiles(userNames= []) {
  let namesLength = userNames.length;
  console.log(namesLength);
}

具名参数

我们可以这么写

function setPageThread(name, options={}) {
}

现在可以给options里的参数取名字了

function setPageThread(name, { popular, expires, ativeClass }={}) {
}
  • 增强了参数可读性

  • 避免 boilerplate codes

  • 没有传递的参数为 undefined

Rest Parameters

这个不知道怎么翻译,即把传参时末尾的若干个参数一起吃掉的一个行为,觉得JS在这一点上,越来越像Ruby了。

比如我们这样定义一个方法:

function displayTags(targetElement, ...tags) {
  let target = _findElement(targerElement);
  for (let i in tags) {
    let  tag = tags[i];
    _addToTopic(target, tag);
  }
}

注: `...`是ES2015中新增加的 `spread` 操作, 以后会多次用到。

那么,我们就可以这样调用这个方法。

```js
displayTags(targetDiv, 'ruby', 'meta', 'structure');

使用箭头方法

对于 =>, 有一句比较很重要的话

Arrow functions bind to the scope of where they are defined, not where they are used. (also known as lexical binding)

简单的说就是,哦耶,我终于不用纠结js的this指代的是什么了。

function TagComponent(target, urlPath) {
  this.targetElement = target;
  this.urlPath          = urlPath;  
}

TagComponent.prototype.render = function() {
  getRequest(this.urlPath, (data) => {
    let tags = data.tags;
    displayTags(this.targetElement, ...tags);
  })
}

这其中的 (data) => { } ,这个function是绑定在 lexcical scope上的,如果用传统的 function(data) {}, 在调用 this.targetElement时,会用这个匿名方法自己的scope。

关于Highcharts

用来HTML SVG画图的JS库,目前用过两个,C3和Highcharts,相比较起来感觉C3太难用,需要设定的参数太多,虽说这样一来你什么都可以自己控制,但是开发起来真心慢且烦躁(所以才会有个D3,啊哈哈)。最近用了Highcharts,感觉还不错,例子和文档都比较清爽,画图的各种参数也是相当简洁(唔,但是参数并不少),比C3真是舒服多了。配合coffee script 食用风味更佳。

文档在这里: http://api.highcharts.com/highcharts

这个Demo也不错: http://www.highcharts.com/demo/combo-dual-axes

用来在线调试的一个例子: http://jsfiddle.net/1xwdmnsn/

Redis入门心得

Nosql的概念已经有很久了,只是最近才真正开始用到Redis。

12年底接触到Redis,那时候还用的是2.6.7,但我们只是在初始化项目的时候,把地址信息存进去(省市区街道这种),方便快速读取,除此之外也未做他用,也未深入研究

最近一个项目开始真正用到Redis,这时的版本已经是3.0.x。我们要读取一个第三方的数据,然而他本身的速度相当慢,所以这里用Redis来做一个快速的缓存,同时也存储一些本地的设置。

入门的书籍

我比较推荐 Redis入门指南 这本书。

我是比较推荐看书学,而且这本书很薄,很快就能翻完。而且作者为了能够成书,会组织一套完整的体系,也算是面面俱到。网上的一篇篇文章,往往只能讲到一个点,并不能涵盖所有。

说实话,我并没有看过其他的书。所以也不好和别的书比较。这本书是和一个小朋友逛书店时看到的,当时翻了下觉得还不错,不过那时并没有用Redis,所以也没有买,只是用微信扫了条码加做收藏。最近要使用时,才从网上下了单。

基本概念

我想一般人对K-V的概念还是有的,不必多提。

虽然value可以是任何东西,但其实Redis是有数据类型的。Redis有五种数据类型,并且不是可以嵌套的(我所谓不能嵌套,是指不能有集合里元素是散列表这种):

  • 字符串
  • 散列
  • 列表
  • 集合
  • 有序集合

之所以了解数据类型,是因为Redis不同的命令是会针对不同的数据。这里你可以看到Redis的命令API,你要先知道你要操作的是什么数据类型,然后才好知道你要选什么命令。

一般来说,不同数据结构的命令,也可以直接从命令本身区分出来,比如散列(Hashes)的操作命令就都是“H+一个单词”的格式,比如HSETHGETALL之类的;又或者列表的命令都是以L(List)开头,比如LRANGE。集合是S(Sets),有序集合是Z(Sorted Sets)。

new Date()在Firefox和Chome下不同效果

最近一个项目需要画图表,我们的D3js,遇到一个很神奇的问题,就是在Chrome上能显示出来的点/线,在Firefox下面显示不出来。

初步察看原因,发现是点坐标的计算结果不一样。我们的X轴是时间轴,同样的日期在Chrome下计算的X坐标是正常的坐标,而在Firefox下计算出来的坐标是负值,导致无法在graph上显示出来。

后来仔细察看,发现是new Date()在两个浏览器下的返回值不一样。

Firefox 37.0.1

new Date("03/23/15, 03:35 AM")
=> Date {Tue Mar 23 1915 03:35:00 GMT+0800 (CST)}

Chrome 41.0.2272.118 (64-bit)

new Date("03/23/15, 03:35 AM")
=> Mon Mar 23 2015 03:35:00 GMT+0800 (CST)

比较下来就是,如果使用两位年份,比如15年,Firefox会当作1915年计算,而Chrome会按照2015年计算。而88年,Firefox和Chome都会当作1988年计算。

Vim查找工具Ack

每次用vim的vimgrep时都觉得挺郁闷的,感觉命令复杂总记不住。

于是在同事的建议下,我试了试Ack.vim,果然觉得还不错。只是我在安装的时候遇到一些小问题。

这里有一篇文章讲如何安装Ack: How to install ack

Ack.vim依赖ack(>=2.0),但是我在Ubuntu下按照说明使用命令 $ sudo apt-get install ack-grep 安装时,总是给我安装的1.9x的版本。

最后就只能按说明里的另一种方式去安装:

curl http://beyondgrep.com/ack-2.14-single-file > ~/bin/ack && chmod 0755 !#:3

不过我还是有路径问题和权限问题,索性分几步来就好了:

$ curl http://beyondgrep.com/ack-2.14-single-file > ack
$ sudo mv ./ack /usr/bin/ack
$ sudo chmod 0755 /usr/bin/ack`

然后检查一下版本:

$ ack --version
ack 2.14
Running under Perl 5.14.2 at /usr/bin/perl

到这里便好了,然后按部就班俺转ack.vim即可。

Vim插件管理工具Vundle

最开始接触Vim时,安装插件总是先去官网上下载,然后在看说明安装到某个目录,一开始觉得新鲜,弄得多了之后便觉得挺麻烦的,于是开始寻找插件管理工具,这时候我接触到了Vundle。

Github: http://github.com/gmarik/vundle

按照说明把Vundle装好后,以后每次要装插件时,只要把github地址写到 .vimrc里,然后在vim里执行 :BundleInstall就可以了。

比如安装NERDtree时,只需要把下面这行加到文件里再执行命令即可:

Bundle 'scrooloose/nerdtree'

Selenium driver 对 JS Confirm 的处理

在Web开发中经常会使用JS弹出确认框,在feature测试脚本如何处理这个弹出框呢?

# ruby
page.driver.browser.switch_to.alert.accept # => 点击OK

page.driver.browser.switch_to.alert.dismiss #=> 点击 Cancel

page.driver.browser.switch_to.alert.text #=> 返回弹窗文字

参考: selenium-webdriver文档

Rails权限验证工具Pundit

在人们开始使用Rails 4之后, cancan的复杂以及兼容性修复不及时而遭人诟病,大家将目光投向了新的工具 Pundit

Pundit是一个纯ruby的gem,用于权限验证。

基本思路

Pundit针对当前模型对象,以及操作,会去相应的模型的policy中寻找操作验证的方法,继而实现验证。

也就是说,只要是任何一个class,需要对他的实例进行验证,只要将验证规则写在对应的policy中即可。

安装及初始化

# Gemfile
gem `pundit`

$ bundle install

$ rails g pundit:install 生成默认的policy文件,路径为app/policies/application_policy.rb

在ApplicationController里添加:

class ApplicationController < ActionController::Base
  # ...
  include Pundit
  # ...
end

添加验证

如果要针对Article模型的实例,进行权限验证,则继续执行:

$ rails generate pundit:policy article => 生成 app/policies/article_policy.rb

# app/policies/article_policy.rb
class ArticlePolicy < ApplicationPolicy
  class Scope < Struct.new(:user, :scope)
    def resolve
      scope
    end
  end
end

假设在articles_controller里有这么一段:

def update
  @article = Article.find(params[:id])
  # 我们要在这里验证 current_user对这个@atilce是否有权限
  @article.update_atttributes(article_arrtibutes)
end

我们在ArticlePolicy添加一个方法,来验证用户是否有这个权限。方法的命名习惯于以问好结尾。

# app/policies/article_policy.rb
class ArticlePolicy < ApplicationPolicy
  class Scope < Struct.new(:user, :scope)
    def resolve
      scope
    end
  end

  def update?
     record.creator_id == user.id
  end
end

这其中 uesrrecord来自于ApplicationPolicy。

class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end
end

然后在ArticlesContoller里加入验证:

def update
  @article = Article.find(params[:id])
  authorize @article, :update?
  @article.update_atttributes(article_arrtibutes)
end

由于action_name和验证方法的名字相同,所以可以简单写成:

def update
  @article = Article.find(params[:id])
  authorize @article
  @article.update_atttributes(article_arrtibutes)
end

这时,我们已经有了权限验证,当用户不具备权限的时候rails会抛出错误;不过我们要做的还没有结束,我们需要捕获这个错误并进行相关处理。

我们的application_controller应该变成这样:

class ApplicationController < ActionController::Base
  # ...
  include Pundit
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    redirect_to root_url, :alert => "You don't have permission to those resources."
  end

end

这样就可以在用户不具备权限时,跳转至root_url并给出相应提示了。

测试

参考这里: Testing Pundit Policies with RSpec

接下来我们对刚才的操作进行测试,以rspec为例:

首先,在spec_helper加入:

require 'pundit/rspec'

然后,生成测试文件,置于spec/policies/article_policy_spec.rb

require 'spec_helper'

describe ArticlePolicy do
  subject { ArticlePolicy.new(user, article) }

  let(:article) { create :article }

  context 'for a guest' do
    let(:user) { nil }

    it { should_not allow(:update) }
  end

  context 'for the write ' do
    let(:user) { article.creator }

    it { should     allow(:index) }
  end

  context 'for an other writer' do
    let(:user) { create :user }

    it { should_not allow(:update) }
  end
end

注意:这里的allow方法并非Pundit提供,而是我们自定义的matcher:

# spec/support/pundit_matcher.rb

RSpec::Matchers.define :allow do |action|
  match do |policy|
    policy.public_send("#{action}?")
  end

  failure_message_for_should do |policy|
    "#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}."
  end

  failure_message_for_should_not do |policy|
    "#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}."
  end
end

从联合国年终回顾(2013)里学习表达

每年年初,联合国都会对过去的一年的大小时间做一次回顾,并会有一份现场的演讲。这份演讲稿对于学英语的人来说,是不可多得的材料。

官方视频(含script): 2013 Year in Review (English)
中文翻译: 联合国2013年度回顾

我自己过了一遍之后觉得有一些可以学习的表达,这里分享一下。

句子于表达分析

After months of deadlock, the Security Council finally united to endorse the expeditious destruction of Syria’s chemical weapons program and to call for all parties to negotiate a political solution to the crisis.

unite to do sth 联合去做(很简洁的表达)
call for all parties to... 呼吁各方...

UN Secretary-General Ban Ki-moon: “This is not a licence to kill with conventional weapons. All the violence must end. All the guns must fall silent.

这句上用词没什么复杂的点,但是我很喜欢潘基文说的这一句 All the violence must end. All the guns must fall silent. 感觉上句子短促且对仗,有气势。另外就是 All the guns must fall silent 感觉很具体,不知道算不算是借代这种修辞。

UK Foreign Secretary, William Hague: “The failure of the council to tackle the crimes committed on a daily basis has resulted in a culture of impunity in which a brutal regime believed it could get away with murdering its own men, women and children.”

impunity [ɪm'pjunəti] n. 不受惩罚;无患;[法] 免罚
brutal ['brutl] A brutal act or person is cruel and violent. 残暴的
get way 可以理解为“逍遥法外”,也是很简单的表达
men, women and children 英语里的表达很有意思,在政治上的演讲里,比如奥巴马的各种演讲,要指代人民的时候,通常不会单用一个people, 而是会有很具体的人 men, women and children,比如这里的,又或者 the old and young 这样的表达。

The first-ever high-level Meeting of the General Assembly on Nuclear Disarmament heard a keynote address by
Iranian President Hassan Rouhani: “The world has waited too long for nuclear disarmament. ..."

hear 这里又是物作主语的一个表达,在中学应该学过,实际用的也很广泛, 类似的 the 21th century see the great development of ... 之类的,虽然看的懂,但是要自然的用出来还是需要努力。不过这一句,如果要翻译成汉语,就要把主语换成人,因为汉语里没有这样的表达,需要翻译成:“伊朗总统鲁哈尼在...进行了主旨演讲”
nuclear disarmament 核裁军

After lengthy negotiations, six world powers and Iran reached an initial agreement in November that calls on Iran to limit its nuclear activities in return for limited relief from sanctions.

six world powers 对于大国家,可以用*world power*去指代,这里可以简单翻译成“六国”
in return for 作为对...的回报,在这一句中,对伊朗这一方来说,是指以“限制核计划”来换取“解除制裁”

Time is running out. The window of peace is narrowing. Opportunities are diminishing. The current round of negotiations is to be a last chance to realize a just peace.

这句话是巴勒斯坦总统阿巴斯说的,可翻译为 “时间已经不多了,希望之窗正在缩小,机会正在减少,目前这轮谈判似乎是实现了公正和平的最后机会”,我觉得说的挺好(嘛,不过巴以冲突什么的,我还是站在以色列这边的)

Increased security and a democratically elected president have brought more stability to a formerly failed state

依然是喜闻乐见的名词作主,拟人化,“不断改善的安全水平和民选总统的上任为这个曾经支离破碎的国家(指经历了20年无政府状态的索马里)带来了稳定”,*failed*在英文是选了一个简单的词,但是对应汉语就不太容易选词,有一种“山河破碎风飘絮”的意味,这里翻译成“支离破碎”。

Angelina Jolie, Special Envoy of the High Commissioner for Refugees, urged the Security Council to have zero tolerance for sexual violence: “Rape, as a weapon of war, is an assault on security and the world in which these crimes happen is one in which there is not, and never will be peace.”

zero tolerance 零容忍
另外,没错,这个*Jolie*,就是你所知道的那个安吉丽娜·朱莉,这里她是作为“难民事务高级专员特使”在年度回顾上发表声音。

当然,联合国年度回顾也不一定全是严肃的话题,比如印度这条:

The first World TOILET Day was celebrated in November to remind that 2.6 billion people live without proper sanitation. More people have access to cell phones than to a toilet. India’s “Total Sanitation” campaign uses the winning slogan: “No Toilet – no bride!”

世界厕所日,阿哈哈哈哈,还有“无厕所,无新娘”的口号也是有意思。

We speak often of hope. Our duty is to turn hope into action through hard work, commitment, skill and integrity. With passion, but most of all with compassion we can build a future your people want and that our world needs.

这是年终回顾的最后一句话,由潘基文所讲,就拿这句作为结束吧: “我们经常谈及希望,我们的责任是,通过辛勤的努力、决心和毅力、技能及正直将希望化为行动;满怀热情于同情之心,我们可以构建一个你们的人民想要和我们这个世界需要的未来”。

专有名词

Foreign Secretary 外交大臣(英国)
Secretary of State 国务卿(美国)
Arms Trade Treaty 武器贸易条约
Middle East Peace Talks 中东和平会谈
M23 movement M23运动,是指3月23号在刚果(金)东部地域的非政府军事集团和政府爆发军事冲突,导致大量无辜人员伤亡的事件,也可写作 M23 rebels
OPCW 禁止化学武器组织(Organisation for the Prohibition of Chemical Weapons)

Arduino初体验

硬件

知道有Arduino这个东西还是在2012年的RubyConfChina上,时隔一年之后忽然记起,既然觉得很有趣,为何不试一试呢。然后我这里写了点我第一次玩这东西的经历,算不上详细,只是记录了 我这么个搞软件的也能玩玩电子元件 的感觉。

在淘宝还有亚马逊上都有搜索过,Arduino的板子还是挺多的,不过出于方便的考虑,还是买了《Arduino电子设计实战指南》这本书配套的套件,每个元件都单独包装着,而且还有标签标注。

这个套件里的板子是用的DFRobot公司的Dreamder Nano,属于是Arduino Leonardo的兼容板。

开发环境

Arduino官网上的下载标签下可以看到有IDE的下载链接,Linux/Windows/Mac OS都有对应的版本。鉴于我看的攻略上是用的Windows,所以接下来的一些操作我也用的Win7去做的。

我这里下载的是zip压缩包,解压后里面有个arduino.exe的文件,点击之后就可以打开Arduino的IDE。这个压缩包中还有几个文件夹:drivers下面放的是板子的驱动;examples里面是例子;libraries里面有库文件。

用USB连接线把Nano连到电脑上之后,Win并不能正常安装硬件驱动;我们需要手动安装下,指定驱动程序路径到刚才解压之后的drivers文件夹即可。

Arduino的Hello World

LED灯的频闪即为Arduino的Hello World。好吧,说实话,我把电路连好之后,那个灯就已经在闪了,竟然自带 鬼畜 闪烁,那我们改下闪烁的频率好了。

void setup()
{
  pinMode(7, OUTPUT);  //我用的7号脚
}

void loop()
{
  digitalWrite(7, HIGH);
  delay(2000);
  digitalWrite(7, LOW);
  delay(2000);
}

loop()中有4行代码,首先是给7号脚输一个高电压,延迟2秒,然后再给7号脚输出低电压,再延迟2秒。循环之后的效果就是,LED灯会隔两秒亮两秒。在IDE里将这段代码写好之后,就可以点击Upload上传到板子上了;在此之前最好Verify一下代码,不过如果代码有问题,也是没法上传的。

截个IDE的图:

效果就是这样,咳,JPG图片不会闪动, 请自行脑补LED灯闪烁的画面 。另,USB输出的是5V,所以给LED灯串联了一个330欧的电阻。

P.S Arduino的板子上有个可复位的熔断器,当电流达到500mA时就会自动断开,以保护计算机USB接口。

下次倒腾倒腾怎么用Ruby去hack。完毕,碎觉!

Nginx部署时Assets静态文件请求的配置

将Rails项目部署到production环境时,在asset方面遇到的问题似乎不少。

现在你们看到的Hegwin.me是用Nginx和Thin部署的,因为也没有太多机会要部署,所以我基本上都是手动来的。可是在执行完 RAILS_ENV=production bundle exec rake assets:precompile,并且以production模式启动时,assets(CSS,JS还有图片)的请求全是404 Not Found。

很不可思议的是为什么Unicorn下面都没遇到这种情况呢?

在production的配置下有这么一段:

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

将这里的false改为true也能解决问题,可是这些静态文件还是用Apache或者Nginx来做比较好。

在Rails Guide的 The Asset Pipeline里面写了如何用Nginx去管理,需要在Nginx配置中加上这一段:

location ~ ^/assets/ {
  expires 1y;
  add_header Cache-Control public;

  add_header ETag "";
  break;
}

抄下来之后依然不对,继续404。

而后在网上搜了之后,看到有的人的解决方式是加入在location配置里加进root来指定public路径,即

location ~ ^/assets/ {
  expires 1y;
  add_header Cache-Control public;
  root /srv/blog/public;

  add_header ETag "";
  break;
}

至此,问题也就解决了。

Git 合并最后若干次提交

在本地进行功能开发的时候,有时候完成某一部分就会commit一次,这样子对自己追溯问题应该比较方便。但是在整体上,小而多的提交对团队中的其他人来说并不太友好,因此,最终把代码push到服务器上时,最好是对一个task或者story,把相应的提交合并起来。而 squash 就可以帮到我们。

比如说我现在在push到服务器之前,把当前分支最后三个commits合并一个commit:

git rebase -i HEAD~3

不过在此之前的话,还是建议设置一下 git 的编辑器,nano实在是用的不习惯

git config --global core.editor "gvim -f"

打完 git rebase -i HEAD~3 弹出的编辑应该是下面这个样子:

对于继续保留的commit,我们可以是用 pick (其实默认都是pick), 而对于需要被合并的提交,将前面的 pick 改为 squash 即可。不过,至少要有一个 pick 或者和 pick同效果的存在。

看图的话,其实不同命令的效果,他自己也有注释的,就不需要我这里多言了。

比如我这里可以这样:

利用Ruby调用TTS进行文本发声阅读

如题,之所以有这么一想法,是因为想找个单词报听写的app,但是一直找不到合适的,不如自己试试搞一个。
于是就去找找调用TTS(text to speech)的工具了。

Liunx:

Linux下面可以使用Festival TTS
参考 festivaltts4r

require 'festivaltts4r'
"I love you".to_speech 

Windows:

Win7应该都默认是有一个TTS的,我们直接调用系统的就行……先安装一个货

gem install win32-sapi

用的时候很简单:

require "win32/sapi5"
include Win32
v = SpVoice.newv.Speak("Let's go for lunch") 

据说Mac自带的TTS非常不错,有机会再试试。

jQuery对DOM的基本操作

1. 查找

1.1 基本选择器

$('p');
$('div#header');

1.2 子选择器

$("#destination li"); //这样会选择ul#desination下所有的<li>,但是我们只向选择他的直接子节点,而不是他的子节点的子节点
$("#destination > li"); // select the only direct children

1.3 Mutiple Selector

$(".promo, .france");

1.4 CSS-like pseudo classes

$("#destination li:first");
$("#destination li:last");
$("#destination li:odd"); //index从0开始,所以这个会选择第0,2,4个li
$("#destination li:even");

1.5 Traversing

$("#destination").find("li") // $("#destination") -> selection; find("li") -> traversal; 会快一些
$("li").first() //比$("li:first")更快

1.6 Walking the DOM

$("li").first().next()
$("li").last().prev()
$("li").first().parent()
$("#destination").children("li") //children()和find()不同,children()只选择直系子节点

2.操作DOM

2.1 插入元素 (appending)

var price = $('<p>$399.99</p>') // create a node
var newOption = $('<option>', { value: 'id', text: 'name'}) // create new note with attributes

用于插入节点的话,可以使用 .append() .prepend() .after() .before()

$("div").append(<element>)
加在d iv节点内部的底部
$("div").prepend(<append>)
插入div节点内部顶部
.after(<element>) .before(<element>)
//插入当前节点相邻的前后

反之,对于被插入的元素,有对应的方法
appendTo(), prependTo(), insertAfter(), insertBefore()

2.2 移除元素

.remove()

Rails项目持久集成工具:RSpec+Guard+Spork

RSpec是Rails的一套测试框架。
Guard,人如其名,监视着项目中的所有文件,当项目中某个文件发生变化,便会根据配置去启动相应的测试脚本。
Spork,可以将其认为是一种服务,自动加载了Rails项目所有文件,当文件发生变化时才会重新加载部分文件,这样就减少了测试启动时加载的时间,大大缩短了测试的准备时间。当然随着Ruby2.0的到来,这个组件的必要性可能就没那么大。

他们三者在一起的作用就是,当你把代码push某个跑着Guard服务的测试机上,他会自动运行相应的测试脚本,并将结果按照某种预定的好的方式传达给开发人员。甚至在配合其他组件之后,便是CI的范畴了;也可以做到优先启动上次失败的测试脚本之类的行为。

RSpec

这东西吧,还算好上手,是Rails项目比较常选择的测试框架。
不过这里并非介绍Rspec本身的,所以对其不做过多的说明

Guard

安装

gem 'guard-rspec'添加到Gemfile中,然后 bundle install

另外,我们还需要一些特定的gem,这些gem依赖于系统。Mac用户可能还要先安装 Growl and growlnotify 。好了,说白了,下面这些东西无非就是调用系统的通知功能。

# Test gems on Macintosh OS X          
gem 'rb-fsevent', '0.9.1', :require => false
gem 'growl', '1.0.3'                      

# Test gems on Linux          
gem 'rb-inotify', '0.8.8'
gem 'libnotify', '0.5.9'

# Test gems on Windows          
gem 'rb-fchange', '0.0.5'
gem 'rb-notifu', '0.0.4'
gem 'win32console', '1.3.0' 

初始化

$ bundle exec guard init rspec 

配置

初始化之后会在根目录有个Guardfile的文件,里面主要就是“什么文件变化后,改进行哪些测试”。

启动

$ bundel exec gurad 

然后你可以随便改个文件试试,看他有没有运行相应的测试。

Spork

这个东西有好有坏,预加载的结果是测试的准备时间减少了,但是如果某些配置文件修改之后,可能要重启这个服务才行。
不过效果是明显的,下面是相同的测试的用时对比:

使用前:

real 0m3.743s
user 0m3.388s
sys 0m0.184s

使用后

real 0m0.746s
user 0m0.316s
sys 0m0.024s

Spork可以与Guard相结合,可以直接启动Guard,他会自己去运行Spork服务。

安装:

gem 'guard-spork'
gem 'spork' 

引导(配置):

$ bundle exec spork --bootstrap 

执行了上面这个命令之后,spec/spec_helper.rb 文件会有变化
现在需要把这个文件内原先的内容,放到Spork.prefork的block中。

Spork.prefork do                                                           
  # ...
end 

运行:

$ bundle exec spork                         
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989! 

这时候跑测试时,在后面加个参数--drb就可以看到效果了。

Guard with Spork

$ bundle exec guard init spork 

这个命令执行完之后Guardfile会被修改,不过我们一般不用改什么。
然后启动guard时,spork就会同时被启动了。

$ bundle exec guard

Git修改最后一次提交

人非圣贤,孰能无过。有时候git commit提交代码之后会发现有遗漏的内容。这时候如果还没有push,我们可以利用 amend 比较方便地修改最后一次提交。

git commit --amend

如果只是修改提交时写的commit message,那只要在开启的编辑器中修改一下文字,然后保存关闭即可。

如果修改或者添加了文件,只需要将需要提交的内容放到stage中,然后在提交就可以了,比如:

git add .
git commit --amend

如何Sinatra部署项目

添加程序入口

Sinatra和Rails一样都是基于rack的应用,在部署前,在sinatra项目的根目录下面加上一个config.ru的文件,内容如下

require "myapp" # 项目主文件
run Sinatra::Application # 如果是定义的子类,则直接用自己定义的类的名字

这之后的过程基本就和rails的部署没有什么不同了,以下以 Nginx + Passenger 为例。

安装 Nginx 和 Passenger

gem install passenger
sudo passenger-install-nginx-module

Nginx加入服务和自启动

wget https://raw.github.com/gist/1548664/53f6d7ccb9dfc82a50c95e9f6e2e60dc59e4c2fb/nginx
sudo cp nginx /etc/init.d/
sudo chmod +x /etc/init.d/nginx
sudo update-rc.d nginx defaults

配置 Nginx(/opt/nginx/conf/nginx.conf)

server {
        listen       3000;
        server_name  localhost;


        location / {
        root /srv/my_app/public;
        passenger_enabled on;
        }

抓取JS延迟加载的页面内容

最近自己想做一个小网站(WoW相关),需要用到的数据需要从别人的公共数据库去取,懒于申请API权限,打算直接从别人现成的数据库爬过来。

于是按部就班用open-uri和nokogiri,结果发现悲剧了!

require 'open-uri'
require 'nokogori'

url = "http://db.178.com/wow/cn/battlepets.html#battlepets:50"
doc = Nokogiri::HTML(open(url))

doc.css("div.list-table")
=> []  竟然是空的,可是我明明浏览器里打开是有的

后来仔细看了下那个页面,表格里的内容原先是存储在JS里面的,只有等JS运行完毕,内容才会显示出来。而URI这里的open,并不会运行js,所以抓去不到对应的内容。

巧的是,刚好群里有个人问,为啥他用的watir 4.x的时候,会提示没有Watir::IE。

灵感不就这么来了么,用Watir::Browser模拟人去打开这个页面,然后把这个页面撸一遍(这个动词真好用),延迟加载的内容也出来了不是,再去解析就OK了嘛~

require 'watir'
require 'nokogiri'

ff = Watir::Brower.new
ff.goto url

ff.div(:id, "footer").wd.location_once_scrolled_into_view
# location_once_scrolled_into_view 是Selenium::WebDriver::Element的一个实例方法,从名字可以看出他是滑动到这个元素的位置
# 这里我们是用它一撸到底,这样子即使是那种需要滑某处才加载的内容也能顺利显示了

doc = Nokogiri::HTML(ff.html)
# 然后按正常情况解析就好

Linux批量修改文件扩展名

背景:同事整理的几千个pdf文件上传到服务器之后,才发现里面的扩展名有的是大写.PDF,有的是小写.pdf,真是令人无比纠结,因为程序是只认小写的……而这在win下完全没看出来。

好在文件都在同一目录下,基本用rename这个命令就可以搞定了。

CentOS下:

rename .PDF .pdf *.PDF

好吧,我们的测试服务器是CentOS的,正是服务器是Ubuntu的,似乎rename这个命令还不太一样。

Ubuntu下:

rename 's/\.PDF/\.pdf/' *

我如何跑起步来

前些天挑战了一下10km长跑,历时67分,可能成绩在经常跑步的人来说不是那么好,可是这却是我第一次跑那么远的路程。

我是怎么跑起步来,这大约要从本人还是一个胖纸说起……

还记得是高中最后一次运动会,那时候我纯粹只是想自我实现一下,背着190斤的躯体,报名参加了1500m和实心球——当时的想法只是,如果这次都不把握机会,那我岂不是在高中连一次运动会都没参加过了,其实我初中就没参加过任何体育活动了,广播操除外,自从我胖起来之后,所有的运动都与我无缘——当然,我现在减肥算是成功了,今年年初去表演Poker Face的时候只有119斤,那是最瘦的时候。

或许时代比较久远了,我也不太记得那次运动会上跑1500m的感觉,但是我记得我在跑道上……呃,挣扎时,几乎所有的人,当我跑过他们身边时都会为我加油,我每次想要减速时,都会有好多人,更多的是不认识的同学为我鼓劲。以至于,那后来的好几天,我脑海中总是会浮现出那个喧嚣的场景,自己已经上气不接下气,整个人就像被放在烤箱里,但是那些人的笑脸,他们呐喊助威的神情,一直让我无法放弃,我拼了老命往前冲。

结果么,我依然是最后一名,常年不怎么运动加上极度肥胖导致我身体素质不怎么样。可是,那真的是我中学以后——准确说是发胖以后,跑的最快的一次。1500m跑完大概5分钟左右的样子,我在大学里跑1000m再也没达到过那个水准。

小时候,经常一群孩子满街跑,我想我那时候的身体一定是很好的。记得有次被老师罚操场跑10圈,我是一路冲下来的。不过长大后,呃,变肥后,我一直觉得跑步蛮痛苦的,但那次运动会的1500m,那种一定要坚持下去的感觉,让我对跑步有了一种特殊的感情。