使用云计算资源玩编程

一、那些云概念

一图胜千言,最近这两年大肆炒作的云计算本质上就是下面这张图。基础设施即服务(IaaS),平台即服务(PaaS)和软件即服务(SaaS)。

他们之间的区别和联系从下图的“食物链”上可以看出来,本质上都是对资源的管理和使其可供消费。

二、我对云计算的理解

先讲个段子,一位中国留学生去美国打工时当过报童,不带计算器,习惯动作抬头望天时心算找零。顾客大为惊讶,纷纷掏出计算器验证,皆无误,也抬头望天,惊恐问:云计算?该留学生茫然。有一天,一位面熟的老外向他打招呼,他望着天想了一会才叫出老外的名字,众人大惊:云存储?

上面这个段子,也从一定层面上反映出云这个概念的炒作性,以至于很多人忽略了本质,被各个厂商的口号忽悠过去,现在任何服务都想和云沾边。

再贴一个微博上的对联,@云计算风云:上联:云计算、云手机、云存储、云服务器、云杀毒、云办公、云优盘、云打印、云邮件、云输入法、云播放器、云游戏—云里雾里;下联:谷歌云、微软云、苹果云、脸谱云、亚马逊云、IBM云、百度云、阿里云、腾讯云、360云、搜狗云、华为云—不知所云;横批:神马都是浮云!

我对云计算的理解就是:平台开发商出售计算能力,且想方设法让这些计算资源可以方便的取得和管理;应用开发者按需消费所需要的资源/服务,架构于这样的平台上的应用也可以获得高可用高扩展的特性。

下面摘自wikipedia,列举了这三种类型的服务各自比较有标志性的厂商或产品

IaaS: Amazon CloudFormation (and underlying services such as Amazon EC2), Rackspace CloudTerremarkWindows Azure Virtual Machines,Google Compute Engine. and Joyent.

PaaS: Amazon Elastic Beanstalk, Cloud FoundryHerokuForce.comEngineYardMendixGoogle App EngineWindows Azure Compute and OrangeScape.

SaaS: Google Apps, innkeypos, Quickbooks Online, Successfactors Bizx, Limelight Video Platform, Salesforce.com, Microsoft Office 365 and Onlive.

三、有人应该要问了我们在玩的OpenStack是什么?

OpenStack是为搭建IaaS平台服务的开源软件,让任何人都可以自行建立和提供云端运算服务,在我们这的作用是建立企业内部的私有云。如果我不关机OpenStack用到的技术细节的话,看上去就是虚拟化和装机。这步走出去成功以后,对我们的好处就是以后可以很方便的拿到一套产品的开发或运行环境。

四、技术层面的那些云计算资源

无论如何,下面这些服务都值得你点开链接,了解一下: Amazon EC2,Amazon S3,Google App Engine,appfog,engineyard,Heroku

五、使用Parse云环境编程

5.1 为什么选Parse这个平台呢?

1. 因为它是Gigaom网站评出来的2012年最值得关注的云计算新兴公司之一,他的模式和appfog比较类似,但是专注移动应用领域,除了Rest API还提供了Android和iOS平台的API,相对来说上手更简单,更适合我们这个Demo

2. 一个web应用除了部署环境之外,还有不得不解决的就是数据库存储的问题,这里我们想使用Parse做云端数据存储。

5.2 构建数据模型

理论结束,正式开始,在这里我想演示怎么做一个基于Cloud在线地址簿(with scalable possibility)

需求有两点: 1. 支持在线存储我自己的通讯录 2. 支持多用户注册登录,可以让别人也在线存储他们的通讯录

5.2.1 首先,需要在parse.com注册一个帐号,或者直接用你的Github帐号登录。

5.2.2 然后,登录后会定向到这个页面,https://parse.com/apps,选择”+Create New App”,填一个名字然后确定你的应用就创建成功了。

有了这个Application ID和 Rest Key其实就可以在线创建和存储数据了,Rest API 可以参考 https://parse.com/docs/rest#objects

这里很值得夸赞的是,示例代码里已经直接填好了你的app的ApplicationID和Rest API Key,基本上你就是拷贝下来,粘帖到命令行就可以尝试了。

5.2.3 考虑第一个需求,创建单用户版本的地址簿。


Note:请使用自己的ID和Key来执行下面的命令,这样才能在你刚才创建的应用的Dashboard里看到你的执行效果。

curl -X POST
  -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
  -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
  -H "Content-Type: application/json"
  -d '{"name":"demo","cellphone":"13799998888","address":"No.1068"}' https://api.parse.com/1/classes/AddressBook

然后我们在网页上的管理界面的Data Browser里面可以看到刚刚通过Rest API创建的数据了

上面是创建数据的例子,下面列出增加,查找,删除,更新的全部代码

curl -X POST \
 -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
 -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
 -H "Content-Type: application/json" \
 -d '{"name":"tomcat","cellphone":"13788889999","address":"No.1068"}' https://api.parse.com/1/classes/AddressBook

{
 "createdAt": "2012-10-30T07:53:36.824Z",
 "objectId": "8weFDfQ343"
}

curl -X GET \
 -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
 -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" https://api.parse.com/1/classes/AddressBook/8weFDfQ343

{"name":"tomcat","cellphone":"13788889999","address":"No.1068", "createdAt":"2012-10-30T07:53:36.824Z","updatedAt":"2012-10-30T07:53:36.824Z","objectId":"8weFDfQ343"}

curl -X PUT \
 -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
 -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
 -H "Content-Type: application/json" \
 -d '{"address":"No.1068 Tianshan Road West"}' https://api.parse.com/1/classes/AddressBook/8weFDfQ343

{"updatedAt":"2012-10-30T09:00:33.314Z"}

curl -X GET \
 -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
 -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" https://api.parse.com/1/classes/AddressBook/8weFDfQ343

{"name":"tomcat","cellphone":"13788889999","address":"No.1068 Tianshan Road West","createdAt":"2012-10-30T07:53:36.824Z","updatedAt":"2012-10-30T09:00:33.314Z","objectId":"8weFDfQ343"}

curl -X DELETE \
 -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
 -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" https://api.parse.com/1/classes/AddressBook/8weFDfQ343

5.2.4 考虑第二个需求,创建多用户版本的地址簿,对于用户注册,登录,验证邮箱,重置密码等的基本需求Parse已经帮我们解决了,左边是User相关API文档的截图。

5.2.4.1 注册用户

curl -X POST -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
  -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
  -H "Content-Type: application/json" \
  -d '{"username":"test","password":"test","phone":"415-392-0202"}' https://api.parse.com/1/users

{"createdAt":"2012-10-31T02:45:10.402Z","objectId":"EXmRl19aJq","sessionToken":"3cbkov9ol4o1iaungkmni0ssc"}

5.2.4.2 创建关联
下面这条是更新命令,有两处值得一提,1. 8weFDfQ343 是刚才第一条AddressBook记录的ID 2. “__op”:”AddRelation” 是Parse为我们提供的做关联的内置命令,相当于数据库的外键关联。

curl -X PUT \
  -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
  -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
  -H "Content-Type: application/json" \
  -d '{"owner":{"__op":"AddRelation","objects":[{"__type":"Pointer","className":"_User","objectId":"EXmRl19aJq"}]}}' https://api.parse.com/1/classes/AddressBook/8weFDfQ343

{"updatedAt":"2012-10-31T02:56:40.557Z"}

5.2.4.3 使用一条命令创建地址簿联系人记录并且关联到这个用户
如果创建地址簿记录和关联到用户需要两个命令,就难免显得麻烦了,一般情况下的用例是:用户登录,添加他的地址簿记录,这个关联是可以一步做到的。

curl -X POST \
  -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
  -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
  -H "Content-Type: application/json" \
  -d '{"name":"tim","cellphone":"13799998888","address":"No.1068 Tianshan Road West","cheatMode":false, "owner":{"__op":"AddRelation","objects":[{"__type":"Pointer","className":"_User","objectId":"EXmRl19aJq"}]}}' \

https://api.parse.com/1/classes/AddressBook

{"createdAt":"2012-10-31T02:59:44.762Z","objectId":"Yb7TdS01Mn"}

这个在Dashboard中的效果也非常直观

5.3 创建用户界面

技术上使用Bootstrap+Sinatra搭建,程序源码可以在这里找到 https://github.com/jihao/ab-parse-demo,你可以clone下来使用ruby ab.rb就能在本机跑了,当然你需要有ruby的运行环境。

这里大概解释一下结构,不太讨论技术细节,毕竟了解ruby的应该可以很容易看懂源码,如果觉得这100来行代码很酷,很简洁,立刻可以online,很想知道实现细节的同学们欢迎来找我讨论。

  1. Gemfile是用bundle init创建出来的,这个文件定义了程序依赖的其他gem
  2. views目录定义了所有的视图模板
  3. ab.rb是程序的核心controller

Sinatra是基于Ruby语言的非常简单的web框架,号称以最小精力为代价快速创建web应用为目的的DSL。
我们看一段代码ab.rb里处理用户注册的代码来感受一下氛围,前面几行是简单的参数校验,第15行是使用RestClient访问parse api创建用户,创建成功后重定向用户到登录界面。

post '/signup' do
  name = email = params[:email]
  password = params[:password]
  confirm_password = params[:confirm_password]
  if name.empty? || password.empty? || confirm_password.empty?
    flash[:notice] = "Should not be empty"
    return erb :signup
  end
  if password != confirm_password
    flash[:notice] = "2 fields does not match"
    return erb :signup
  end

  headers = AUTH_HASH.merge({:content_type=>:json, :accept=>:json})
  result = RestClient.post "https://api.parse.com/1/users",
    {"username"=>name,"password"=>password,"email"=>email}.to_json,
    headers
  @result = JSON.parse result
  flash[:notice] = "Succeed (ObjectId=%s)" % @result["objectId"]
  redirect "/signin"
end

5.4 部署应用

我选择使用上文中提到的Heroku来部署这个Sinatra应用,从头开始到部署完成非常简单

5.4.1 打开这个页面 https://toolbelt.heroku.com,选择你的平台安装

hao@ab-parse-demo$ heroku login

5.4.2 在ab-parse-demo的git repository下面,执行如下命令

hao@ab-parse-demo$ heroku create ab-parse-demo

5.4.3 然后就可以通过git push来部署了,这里给不想亲自尝试的同学们show下这条命令的log。
可以看到,首先代码被提交到Heroku上,然后它根据Gemfile自动安装了gem依赖,然后应用被编译部署到 http://ab-parse-demo.herokuapp.com


hao@ab-parse-demo$ git push heroku master
Warning: Permanently added the RSA host key for IP address '50.19.85.154' to the list of known hosts.
Counting objects: 15, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 1.30 KiB, done.
Total 8 (delta 7), reused 0 (delta 0)

-----> Heroku receiving push
-----> Ruby/Rack app detected
-----> Installing dependencies using Bundler version 1.2.1
       Running: bundle install --without development:test --path vendor/bundle --binstubs bin/
       Fetching gem metadata from http://ruby.taobao.org/.
       Fetching full source index from http://ruby.taobao.org/
       Using mime-types (1.19)
       Using rack (1.4.1)
       Using rack-flash3 (1.0.1)
       Using rack-protection (1.2.0)
       Using rest-client (1.6.7)
       Using tilt (1.3.3)
       Using sinatra (1.3.3)
       Using bundler (1.2.1)
       Your bundle is complete! It was installed into ./vendor/bundle
       Cleaning up the bundler cache.
-----> Discovering process types
       Procfile declares types     -> (none)
       Default types for Ruby/Rack -> console, rake, web
-----> Compiled slug size: 892K
-----> Launching... done, v7
       http://ab-parse-demo.herokuapp.com deployed to Heroku

To git@heroku.com:ab-parse-demo.git
   981fb68..30889ab  master -> master

hao@ab-parse-demo$ heroku ps
=== web: `bundle exec rackup config.ru -p $PORT`
web.1: up 2012/11/01 22:54:42 (~ 10m ago)

5.4.4 最终效果

5.4 水平扩展应用

这是heroku管理这个应用的后台,Dynos默认是1,1个节点是免费的,然后这个蓝色条是可以拖动的,如果我要这个应用水平扩展到10个节点以支持更多的用户的话,可以拖动以预览相对应的费用。

六、小结

综上所述,这就是我们触手可及的云计算,对于程序员来说是相当不错的一件事情。

参考

  1. http://en.wikipedia.org/wiki/Cloud_computing
  2. http://parse.com
  3. http://sinatrarb.com
  4. http://heroku.com

Ruby中神奇的下划线

【原文地址】:http://po-ru.com/diary/rubys-magic-underscore
【原文作者】:Paul Battley

【译注】:我从RubyWeeklyNews看到的这篇小文章,挺有趣,比较短就翻译了玩一下,其实英文原文也很简单的

【译文】:
我今天发现当下划线作为变量名的时候Ruby对其处理的方法有点不一样。【译注:我们一般只注意到下划线在IRB中可以作为上一个返回值的容器】

假设,为了参数,我们有个数据集看上去是这样的:

people = {
  "Alice" => ["green", "alice@example.com"],
  "Bob"   => ["brown", "bob@example.com"]
}

我们想忽略眼睛的颜色和年龄,然后将每个元素转成一个简单的包含名字和邮件地址的数组,我们也许会这样做

people.map { |name, fields| [name, fields.last] }

但是这样写的话就不太清楚fields.last是什么了。 Ruby有很强大的解构赋值的特性,所以我们可以用括号到达结构的内容,使得代码看起来更直接:

people.map { |name, (eye_color, email)| [name, email] }

这样好点了,但是eye_color这个未使用的变量在上下文里是个干扰。这并不利于我们理解这段代码的作用。

很多语言(Haskell,Go,Clojure,还有很多其他的)有个惯例是用下划线代表未被使用的变量。Ruby代码风格指南中也这么推荐了(链接1:Christian Neukirchen’s Ruby Style Guide链接2:Github Ruby 代码风格指南

知道这个的话,我们可以这样写:

people.map { |name, (_, email)| [name, email] }

然后我们差不多相当满意了。但是如果我们需要忽略更多的元素呢?假设数据集是这样:

people = {
  "Alice" => ["green", 34, "alice@example.com"],
  "Bob"   => ["brown", 27, "bob@example.com"]
}

没问题。还是重用下划线:

people.map { |name, (_, _, email)| [name, email] }

你不能使用除_之前的其他变量来做这件事情,至少在Ruby1.9中是这样的,只能在变量被命名成_的时候这招才能工作,也许你们都会亲自尝试一下:

people.map { |name, (x, x, email)| [name, email] }
# SyntaxError: (eval):2: duplicated argument name

这个原因在Ruby的解析器里可以找到,shadowing_lvar_gen. 如果变量名称是一个下划线的时候,所有关于变量的通常的检查都会被掠过。

在Ruby1.8中多个下划线也具有上文同样的行为,但是出于不同的原因【译注:我没太理解这句】,Ruby1.8不关心块参数中重复的参数名称的情况。
同时,1.8和1.9都不关心多重赋值的时候参数名重复的情况:

a, a, b = 1, 2, 3
a, a, b = [1, 2, 3]

这两行代码都能正常工作(尽管我建议你不要信赖a的值)。

(感谢Ben发现parse.y中相应的处理代码.)

【译注】值得看的链接
http://stackoverflow.com/questions/9559561/where-and-how-is-the-underscore-variable-specified-in-ruby
http://stackoverflow.com/questions/123494/whats-your-favourite-irb-trick/864710#864710

新浪微博oauth2

前两天基于gem omniauth 1.0,实现了新浪微博oauth2的strategy,项目主页 https://github.com/jihao/omniauth-weibo

测试过程中发现新浪微博开放平台oauth2的两个问题
1. access_token response 不够标准,它返回的 “content-type”=>”text/plain;charset=UTF-8″, 其实返回值是json格式,如果http头返回”application/json” 就更好了,省得开发人员手工处理。

2. OAuth授权后获得access_token,后续验证用户信息调用account/get_uid,微博API要求请求参数里必须填写access_token,这里比较难说是谁的问题,但是比较来说github instagram和oauth2 gem的默认行为都兼容,唯独新浪微博需要手工设置query参数,对程序员的易用性兼顾不够。

rails-oauth-连接新浪微薄记录

这是一个简单的rails程序,技术上主要是devise,omniauth这个两个gem的使用

功能一个注册登陆系统,登陆完成之后,点击新浪图标,oauth连接/授权/关联新浪微薄账号,取得用户的相关微薄数据,上一张完成之后的页面截图就清楚些了。

步骤如下

  1. 使用devise实现注册登陆功能
  2. 使用omniauth实现新浪微薄的oauth认证
    1. 配置omniauth
    2. 完成新浪微薄oauth,保存access_token, access_token_secret到数据库
    3. 使用之前存下来的access_token, access_token_secret调用新浪API取所需要的数据

OK,下面是从创建这个rails app开始的完整步骤

首先是1.使用devise实现注册登陆功能

>rails new authentication
>cd authentication

修改Gemfile
     gem 'devise'
     gem 'omniauth'
     gem 'json'

>bundle install
>rails g controller home index
>rails g devise:install
>rails generate devise User
>rake db:migrate

修改config/routes.rb
    root :to => "home#index"
修改app/views/home/index.html.erb,显示注册、登陆、退出等URL
    <% if user_signed_in? -%> <!-- Provided by devise -->
	<div style="float:right">
	  <%= current_user.email %> |
	  <%= link_to '用户信息', edit_user_registration_path %> |
	  <%= link_to '退出登录', destroy_user_session_path, :method => :delete %> |
	</div>
    <% end -%>
    <% unless user_signed_in? -%>
	<div style="float:right">
	  <%= link_to '注册', new_user_registration_path %> |
	  <%= link_to '登录', new_user_session_path %>
	</div>
    <% end -%>

>rails s

devise真的很好用,这步骤做完之后,基本的注册登陆功能已经完备,可以访问http://localhost:3000 验证一下

接下来2.使用omniauth实现新浪微薄的oauth认证
A. 配置omniauth

修改config/application.rb
    require 'oa-oauth'
    config.middleware.use OmniAuth::Strategies::Tsina, 'APP_KEY', 'APP_SECRET'
    #可以加多个Provider,如果还需要连接其他认证

B. 完成新浪微薄oauth,保存access_token, access_token_secret到数据库
使用omniauth是如此简单,你只需要加上这个link(/auth/:provider)就行了,这里的provider是tsina

修改app/views/home/index.html.erb,增加一行
<div style="float:right">
 <%= current_user.email %> |
 <%= link_to '用户信息', edit_user_registration_path %> |
 <%= link_to '退出登录', destroy_user_session_path, :method => :delete %> |
 <%= link_to image_tag("tsina_24_24.png"), '/auth/tsina', :title=> "连接新浪微薄" %>
</div>

OK,用户登陆完就能看到这个链接了,点一下试试看,首先是一个授权app的页面,这时候输入新浪微博的用户密码,确认授权,然后不出意外你看到的是这个效果

我们再来回顾一下oAuth的流程,这个是新浪提供的一个流程图,我们正处于重定向到Callback_url这个阶段,而我们应用还没有定义回调的url

OK,下一步就是定义我们的callback_url

>rails g controller authorization oauth_create oauth_destroy

修改config/routes.rb
  get "authorization/oauth_create" #上个命令自动生成
  get "authorization/oauth_destroy" #同上
  match "/auth/:provider/callback" => "authorization#oauth_create" #定义callback_url

先看看oAuth成功没有,code可以简单点

app/controllers/authorization_controller.rb
class AuthorizationController < ApplicationController
  def oauth_create
    auth = request.env["omniauth.auth"]
    render :text=>auth.to_yaml
  end
end

再次点击新浪图标,我们能看到一堆文本信息,包括token,secret等等,说明oAuth已经成功了。

C. 使用之前存下来的access_token, access_token_secret调用新浪API取所需要的数据
现在我们需要的数据都能取到了,然后思考一个问题,貌似我们应该把这个授权信息存下来,然后就不需要每次用户都授权一下才能取API数据。
建立下面这个authorization model存授权信息

>rails g model authorization provider:string uid:string user_id:integer access_token:string access_token_secret:string
app/models/user.rb
class User < ActiveRecord::Base
  has_many :authorizations do
     def find_or_create_by_params(params) #辅助方法,别着急,一会儿就看到怎么用的了
          provider,uid = params[:provider],params[:uid]
          access_token,access_token_secret = params[:access_token],params[:access_token_secret]

          authorization = find_or_create_by_provider_and_uid(provider,uid)
          authorization.update_attributes(params.except(:provider,:uid))
     end
  end

  #... devise stuff
end

app/models/authorization.rb
class Authorization < ActiveRecord::Base
     belongs_to :user

     validates :user_id, :uid, :provider, :presence => true
     validates :uid, :uniqueness => { :scope => :provider }
end

OK,回到回调方法里,我们要把有用的数据存到model里进而持久化

app/controllers/authorization_controller.rb
class AuthorizationController < ApplicationController
  def oauth_create
    auth = request.env["omniauth.auth"]
    user = current_user
    user.authorizations.find_or_create_by_params({ #这里调用辅助方法
	:provider => auth["provider"],
	:uid => auth["uid"],
	:access_token => auth['credentials']['token'],
	:access_token_secret => auth['credentials']['secret']
    })
    flash[:notice] = "#{auth['provider']} user #{auth['uid']} successfully authenticated"
    redirect_to root_url
end

然后我们希望每次用户刷新首页的时候,我们去取一下他授权微薄的最新信息(home#index是我们的root_url还记得不)

class HomeController < ApplicationController
  def index
	oauth_retrive
  end

private
	require 'json'
	def oauth_retrive
		user = current_user
		if not user.blank?
			authorization = user.authorizations.first
			if not authorization.blank?
				access_token = authorization.access_token
				access_token_secret = authorization.access_token_secret

				consumer = OAuth::Consumer.new('APP_KEY','APP_SECRET',{:site =>'http://api.t.sina.com.cn'})
				accesstoken = OAuth::AccessToken.new(consumer, access_token, access_token_secret)
				response = accesstoken.get("http://api.t.sina.com.cn/account/verify_credentials.json")
				@weibo_user = JSON.parse(response.body)
			end
		end
	end
end

 

修改app/views/home/index.html.erb,增加读取weibo_user的信息
<% if user_signed_in? -%> <!-- Provided by devise -->
	<div style="float:right">
	  <%= current_user.email %> |
	  <%= link_to '用户信息', edit_user_registration_path %> |
	  <%= link_to '退出登录', destroy_user_session_path, :method => :delete %> |
	  <%= link_to image_tag("tsina_24_24.png"), '/auth/tsina', :title=> "连接新浪微薄" %>
	</div>
	<% if @weibo_user %>
	<div style="clear:both"></div>
	<div style="float:right">
		<p>已连接新浪微薄:<p/>
		<%= link_to image_tag(@weibo_user['profile_image_url']), "http://weibo.com/#{@weibo_user['id']}" %>
		<%= link_to @weibo_user['screen_name'], "http://weibo.com/#{@weibo_user['id']}" %>
		关注:<%= @weibo_user['followers_count'] %>
		粉丝:<%= @weibo_user['friends_count'] %>
		微薄:<%= @weibo_user['statuses_count'] %>
		收藏:<%= @weibo_user['favourites_count'] %>
	</div>
	<% end -%>
<% end -%>

按照这个步骤,就能成功看到最终成果了。

Notes:上文所用的环境如下,完整代码可以在这找到https://github.com/jihao/rails_apps_for_fun/tree/master/authentication

Rails 3.0.9
ruby 1.8.7 (2011-02-18 patchlevel 334) [i386-mingw32]
builder (2.1.2)
devise (1.4.2)
omniauth (0.2.6)

这个过程中找到的较好的参考文档/资源

  1. https://github.com/plataformatec/devise
  2. http://oauth.rubyforge.org/
  3. https://github.com/intridea/omniauth

基于arduino和ruby的gmail邮件提醒

基于arduino和ruby的gmail邮件提醒,我要实现的功能是gmail收到新邮件的时候,arduino板子的红色LED灯亮30秒,同时蜂鸣器叫5声

晚上折腾了一会儿,连调成功了,现在收到新邮件的时候,桌面上的arduino板子会有声有色的提醒啦,有图有真相,O(∩_∩)O哈哈~,下面分享这个小玩具是怎么搞定的。

这个玩具由两部分组成
1.PC端检查gmail邮件更新和发送串口信号的ruby脚本
2.Arduino的信号控制及串口通信程序

1.PC端检查gmail邮件更新和发送串口信号的ruby脚本
用到两个ruby gem ‘ruby-gmail’和’serialport’
前者封装了操作gmail邮件的相关功能,后者是一个跨平台的ruby串口通信库
ruby代码比较简单,就不多解释了

require 'rubygems'
require 'gmail'
require 'serialport'

$username,$password = ARGV

def init_email_count
	gmail = Gmail.new($username, $password)
	$count = gmail.inbox.count(:unread)
	puts "unread email count: #{$count}"
	gmail.logout
end

def has_new_email?
	latest = get_latest_email_count
	if latest!=$count
		$count = latest
		puts "current email count: #{$count}"
		return true
	else
		return false
	end
end

def get_latest_email_count
	print '.'
	gmail = Gmail.new($username, $password)
	latest = gmail.inbox.count(:unread)
	gmail.logout
	return latest
end

def write_serialport chr
	puts "write to serial port [#{chr}]"
	$sp.write chr
	#rd = sp.read
	#puts "read from serial port [#{rd}]"
end

init_email_count
# 0 is mapped to "COM1" on Windows, and 2 is COM3, 9600 is baud rate
# Arduino on serial port2 (COM3)
$sp = SerialPort.new(2, 9600)
loop do
	if has_new_email?
		write_serialport 'H'
		sleep 30
		write_serialport 'L'
	end
	sleep 5
end

#not closed
#$sp.close

2.Arduino的信号控制及串口通信程序
为了有声有色,混合了fade和buzzer两个功能,所以代码略多

PIN 9,是绿色LED渐明渐暗的控制,显示系统正在正常工作
PIN 10,是红色LED,当收到串口H信号的时候亮起来,指示收到新邮件
PIN 6,蜂鸣器的控制针脚

其余的应该都能看懂意思,不解释了

int brightness = 0;    // how bright the LED is
int fadeAmount = 5;    // how many points to fade the LED by
int ledPin=9;          // pin for green LED which indicates system running
int serialLedPin=10;   // pin for RED LED which indicates incoming email
int buzzerPin=6;       // pin for buzzer
int incomingByte;      // a variable to read incoming serial data into
void setup()  {
  Serial.begin(9600);
  // declare pin 9 to be an output:
  pinMode(ledPin, OUTPUT);
  pinMode(serialLedPin, OUTPUT);
  pinMode(buzzerPin,OUTPUT);//设置模拟信号IO脚模式,OUTPUT为输出
}
void beep()
{
  unsigned char i,j;//定义变量
  for(i=0;i<80;i++)//输出一个频率的声音
  {
    analogWrite(buzzerPin,i);//发声音
    delay(1);//延时1ms
    analogWrite(buzzerPin,LOW);//不发声音
    delay(1);//延时ms
  }
}
void beep5times()
{
   for(int i=0;i<5;i++)
   {
     beep();
     delay(1000);
   }
}
void checkSerialInput() {
  // see if there's incoming serial data:
  if (Serial.available() > 0) {
    // read the oldest byte in the serial buffer:
    incomingByte = Serial.read();
    Serial.write(incomingByte);
    // if it's a capital H (ASCII 72), turn on the LED:
    if (incomingByte == 'H') {
      digitalWrite(serialLedPin, HIGH);
      beep5times();
    }
    // if it's an L (ASCII 76) turn off the LED:
    if (incomingByte == 'L') {
      digitalWrite(serialLedPin, LOW);
    }
  }
}
void loop()  {
  // set the brightness of pin 9:
  analogWrite(ledPin, brightness);
  // check any input from serial in
  checkSerialInput();

  // change the brightness for next time through the loop:
  brightness = brightness + fadeAmount;

  // reverse the direction of the fading at the ends of the fade:
  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount ;
  }
  // wait for 30 milliseconds to see the dimming effect
  delay(30);
}

最后总结一下:
虽然刚刚ruby入门,但是感觉用ruby写点小东西满顺畅的,还有就是github,ruby gem神马的都很强大
然后就是arduino的开发上手也很容易

Ruby入门-模块

Ruby模块的用法 1.Mix-in 2.namespace

  • Ruby不允许多重继承
  • Ruby可以继承多个模块
#foobar_module.rb
module FooBarModule     #用module关键字来定义模块
     def say
         puts 'hi'
     end
end

#foo.rb
require "foobar_module"     #如何在不同文件中,需要使用require或者load来加载模块
class Foo
     include FooBarModule    #可以理解为,把Module的内容拷贝一份到类里
     def bar
         puts 'foobar'
     end
end
foo = Foo.new
foo.say #hi
Foo.ancestors #=> [Foo, FooBarModule, Object, Kernel]     #模块的include是一种特殊的继承
FooBarModule.respond_to? :new     #=> false 模块不能实例化
FooBarModule.respond_to? :extend  #=> true  模块可以继承模块,模块不能直接继承Object

方法调用的查找顺序

  1. 对象的单态方法
  2. 对象的实例方法
  3. include模块中的实例方法
  4. 父类中的实例方法
  5. 父类include模块中的实例方法
  6. 一直这样找到最上层,如果还是没有找到,Ruby会调用默认Object#method_missing,产生NoMethodError异常

Ruby入门-类和对象-对比视图

\ Ruby Code Notes
class Foo
     def method1
          puts "I am instance method1"
     end
end
类定义
实例方法 method1
类方法
class Foo
     def Foo.method1
          puts "I am class method1"
     end
end
java中的static方法
单态方法
bar = Foo.new
def bar.method2
     puts "I am singleton method2"
end
bar2 = Foo.new
bar.method2    #I am singleton method2
bar2.method2   #NoMethodError: undefined method `method2' for #
单态方法是指不隶属于某个类,而直接隶属于某个实例的方法
单态类
bar = Foo.new
class << bar
     def method2
          puts "I am singleton method2 defined in singleton class"
     end
end
bar.method2    #I am singleton method2 defined in singleton class
单态类是指只有特定实例才隶属的类,比如上面的bar这个实例所隶属的类
类方法
class Foo
     def Foo.method1 #与单态方法的定义表达式相似"def (对象).(方法名)"
          puts "I am class method1"
     end
end
类方法=Class对象的单态方法
元类
class Foo
     class << self #定义self(=Foo)的单态类,等同于 class << Foo
          def method1
              puts "I am class method1 defined in metaclass(Foo)"
          end
     end
end
Foo.method1 #I am class method1 defined in metaclass(Foo)
Class对象的单态类就是元类

Ruby入门-IO

#!/usr/bin/env ruby

#读取命令行参数,不会包括ruby xx_file_name.rb
expression = ARGV.join(' + ')
total = ARGV.inject(0) {|sum,x| sum+=x.to_i}
total2 = ARGV.reduce(0) {|sum,x| sum+=x.to_i}
puts "#{expression} = #{total} = #{total2}"

#文件读写
File.open(__FILE__){|f|
	contents = f.read
	#puts contents
}

#写模式打开文件
File.open(__FILE__,'a'){|f|
	f.puts "#last-modify-time '%s'" %Time.now
}

#逐行	each_line
#逐字节	each_byte
File.open(__FILE__,'r'){|f|
	f.each_line do |line|
		puts "#{f.lineno}: #{line}"
	end
}

#在不带有代码块的情况下使用File.open,需要手工关闭文件
f = File.open(__FILE__)
f.close

#标准输入输出
#重定向stdout
File.open(__FILE__,'a'){|f|
	$stdout = f
	puts "#last-modify-time '%s'" %Time.now
	$stdout = STDOUT	#将$stdout的初始值改回原样
}

#标准输入输出的初始值
#$stdin = STDIN
#$stdout = STDOUT
#$stderr = STDERR

puts "演示从stdin读取信息"
ARGF.each_line { |line|
	print "[#{line.strip}]\n"
	if line.strip == "end"	#判断字符串内容是否相等
		break
	end
}

Ruby入门-方法-代码块-闭包-Proc-lmabda

方法

方法的定义

def add_method(param1,param2)     #括号可以省略
     p [param1,param2]
     param1+param2                         
     #return可以省略,方法内部最后一个被调用的表达式的值就是返回值
end

多值的返回(比较酷的特性)

def weather_report
     temperature = 30
     humidity = 20
     suggestion1="weather is good for washing"
     suggestion2="weather is good for outing"
     return temperature,humidity,suggestion1,suggestion2
end

使用多重赋值接收多个返回值

>t,h,*suggestion = weather_report       
#=> [30, 20, "weather is good for washing", "weather is good for outing"]
>suggestion                                        
#=> ["weather is good for washing", "weather is good for outing"]

可变参数(java中也有的特性 eg:String…args, String[] args)

def some_method(a,b,*array)
     p [a,b,array]
end
irb(main):047:0> some_method(1,2,3)     #结果 [1, 2, [3]]
irb(main):048:0> some_method(1,2,3,4,5)     #结果 [1, 2, [3, 4, 5]]

代码块-闭包-Proc-lmabda

[1,2,3].each { |x|
p x
}

[1,2,3].each do |x|
p x
end

[{:key1=>:value1},{:key2=>:val2}].each { |hash|
hash.each { |k, v|      #传递两个参数
puts "key=#{k}, value=#{v}"
}
}

一般情况下,调用时,最多只能传递一个代码块
万不得已情况下,可以用Proc.new/lmabda创建Proc对象之后作为参数传递给方法

比较恶心的,在代码块所引用的外部变量,在代码块未消失之前会被保持(保持在仅此proc可见的作用域内)

def create_proc     #书上的一段例子
     count = 1
     return  Proc.new {
count = count+1     
p "count=#{count}"
}
end
proc = create_proc
proc.call     #"count=2"
proc.call     #"count=3"

proc2 = create_proc          
#看下面的结果,证明两次调用create_proc是相互独立的,局部变量互不影响,感觉就是proc维护了自己的值栈
proc.call     #"count=4"
proc2.call     #"count=2"
proc2.call     #"count=3"
> proc.inspect       #=> "#<Proc:0x02cdaae8@(irb):154>"
> proc2.inspect     #=> "#<Proc:0x02cdaae8@(irb):154>"
> proc == proc2     #=> false

yield
ruby中,可以对方法传入一个代码块作为回调,感觉上类似于javascript的事件回调函数
不同之处在于yield控制回调代码块在方法体内,何时执行,并且可以通过传递参数给回调代码块

例如:javascript对按钮事件的处理onclick是个回调函数,当然它何时被调用由浏览器控制

document.getElementById('button').addEventListener('click',
     function onclick(e){alert(e.x+","+e.y)},false);

然后我们用Ruby的yield模拟这个行为,某种程度上可以把下面这个方法当成浏览器的控制代码

def mouse_click     #方法
     puts "mouse click on a DOM element, first get the clicked position"
     point_x = 100     #假设这是我们计算得出的相对位置
     point_y = 200
     yield  point_x,point_y
end

mouse_click {|x,y|     #传递给mouse_click方法的闭包,相当于javascript例子的回调函数
     puts "you clicked [#{x},#{y}]"
}

将代码块作为参数传递
即以对象的形式传递代码块,方法是在方法参数的最后,加上用&标识的参数,表示传递的代码块的Proc对象保持在此参数中

class DomObject
     def add_mouse_click_handler (&handler)
          @handler = handler
     end
     def trigger_click!
          x , y = 100, 200
          @handler.call     x,y
     end
end

irb(main):082:0> obj = DomObject.new                                          
#=> #<DomObject:0x2dc7db8>

irb(main):083:0> obj.add_mouse_click_handler {|x,y| p [x,y]}          
#=> #<Proc:0x02cd5478@(irb):83>

irb(main):084:0> obj.trigger_click!                                                  
#[100, 200]

Ruby入门-String

字符串 String.new
#多行文本,比python简单(python需要这么搞”””  “””),ruby使用’ “都可以,唯一的要求是两端引号要相同
‘abc
def
gh’     #=> “abc\ndef\ngh”
#{expression}”     #{…}的内容会被当成Ruby表达式来解析,它的值会被展开在字符串中(对象的to_s方法会被调用)
     a=2
     “a的5次方是#{a**5}”     #=> “…32″
#反引号字符串,执行操作系统命令,和shell脚本一样
#百分号记法,表示字符串字面量,作用是减少转义字符的使用
     %q[其中含有”的字符串]
     %Q[其中含有’的字符串]
种类:
记号 种类 转义字符和表达式展开有效
%!hogo! 字符串 YES
%q!hogo! 字符串 只有用来表示引用符号(这里的是!),反斜杠的转义有效
%Q!hogo! 字符串 YES
%w!hogo hogo hogo! 字符串数组,元素用空格隔开 用反斜杠转义有空格的字符元素
%W!hogo hogo hogo! 同上 YES
%r!hogo! 正则表达式字面量
%s!hogo! Symbol字面量
%x!hogo! 与反引号字符串意义一样 YES
p <<-EOS     #-的作用是使得间隔符前面又TAB或者空格也能识别,排版需要
multi-line
string
end
     EOS
正则表达式匹配 绑定操作符 =~ (这个感觉和perl的一样)
if /<a href=”(.*?)”[>]/ =~str
     puts “match link “+$1
end
字符串替换
str=”123456789″
str.gsub(/3/,”9″)     #非破坏性,不会改变str本身
str.gsub!(/3/,”9″)     #破坏性,改变str
字符串对比

str1 == str2 #判读字符串内容是否相等
str1.equal? str2 #判断字符串对象是否一样