Code for fun

Do It Right Now!

用NodeJS创建博客

最近,想折腾下nodejs,所以,就想折腾一下nodejs搭建一个blog,现在支持nodejs的主机也还有不少的。比如,heroku目前就支持nodejs的主机,当然,还有其它的一些了。

如何开始

这次,我们选用 node + express(类似ruby的sinatra框架) + mongoDB(NoSQL数据库). 一般来说,express我们一般用npm(node的包管理)进行安装,只有下面简单的命令就可以了。

1
sudo node install express -g

实现功能

在这里,我们主要为了实现功能:

  • 创建新文章
  • 展示所有文章列表

具体步骤

下面,我们就介绍详细的具体步骤。

1
2
3
4
mkdir blog
cd blog
express -c stylus #创建使用jade模版引擎和stylus css引擎的应用
npm install -d  #下载所依赖的包

构建对应的ORM,用于处理文章的CRUD操作

首先,我们得构建对应的操作来处理文章的crud操作,代码如下:

1
2
3
4
5
6
mongoose.connect(process.env.MONGOLAB_URI || 'mongodb://localhost/blog');
var Article = mongoose.model('Blog', new mongoose.Schema({
    title: String,
    body: String,
  }));
//具体的crud可参照 https://github.com/LearnBoost/mongoose#readme

新增文章

首先,我们定义请求/blogs/new页面为新增的页面。 在此页面存在表单元素title和body.我们通过填写对应的内容,然后发送post请求,增加对应的内容。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.get('/blogs/new',function(req,res){
  res.render('blog_new.jade',{locals: {
        title: "新增文章",
        }
        });
  });

app.post('/blogs/new', function(req, res){
    var article = new Article({title: req.param('title'), body: req.param('body')});
    article.save(
      function( error) {
      console.log("there is a error occured!");
      }
      );
    res.redirect('/')
});

查看文章

1
2
3
4
5
6
7
8
9
10
app.get('/blogs/:id',function(req,res){
    articles = Article.find({});
    Article.findById(req.params.id, function(err, article) {
    res.render('show.jade',{locals: {
       title: article.title,
      body: article.body,
    }
   });
  })
});

参考资料

给地图大头针加图片

我们在IPhone中使用谷歌地图的时候,我们可以在地图中插入大头针。这些大头针左边显示了图片,右边一般显示>标记,用于显示更加详细的信息。 下面,介绍下,如何在大头针中加图片。

每个Annotations都可以用一个callout用于显示信息, 默认,我们点击的时候,只显示 titlesubtitle ,但是,我们可以为它增加 左边和右边的accessory视图。一般,我们左侧会增加图片,右边一般增加一个UIButton(UIButtonTypeDetailDisclosure).下面,介绍如何增加:

  • 首先,给地图控制器增加下面方法,用于设置图片
MapViewController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
  //为针上增加左侧图片
    MKAnnotationView *aView = [mapView dequeueReusableAnnotationViewWithIdentifier:@"MapVC"];
    if (!aView) {
        aView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"MapVC"];
        aView.canShowCallout = YES; 
        aView.leftCalloutAccessoryView  = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
    }
    aView.annotation = annotation;
    [(UIImageView *)aView.leftCalloutAccessoryView setImage:nil];
    return aView;
}


-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)aView
{
    UIImage *image = [self.delegate mapViewController:self imageForAnnotation:aView.annotation];
    [(UIImageView *)aView.leftCalloutAccessoryView setImage:image];
}
  • 设置delegate用于传递所需要显示的图片。

具体代码可参见样例代码

构建cocos2d开发环境

cocos2d是一个开源框架,用于构建2D游戏、演示程序和其他图形界面交互应用等。参见百科

  • 首先下载相应的文件,最新的代码可以在github上查看

  • 然后,到对应的目录,在termianl下,执行下面命令

1
 ./install-templates.sh -u -f
  • 然后,在我们创建工程的模版中,我们就嫩看见对应的模版了。如下图所示: image

  • 最后,我们点xcode的运行,就可以看到如下效果了 image

IOS手势操作

当我们使用IPhone的时候,时常通过我们的手势来操作,比如说,我们通过两指挤压使图片放大缩小等,还有我们通过手指划过改变直线的弯曲程度等等。 这些在IOS中都利用到了UIGestureRecognizer类。

如何使用

  • 增加一个手势识别方法给UIView。
  • 提供响应的”handle”方法处理相应的手势操作。

例如,我们在Controller中增加一个手势识别方法给UIView。

1
2
3
4
5
6
7
  - (void)setPannableView:(UIView *)pannableView
      {
          _pannableView = pannableView;
          UIPanGestureRecognizer *pangr =
              [[UIPanGestureRecognizer alloc] initWithTarget:pannableView action:@selector(pan:)]; //为panableView增加手指划过操作
          [pannableView addGestureRecognizer:pangr]; //相当于开启对应的手势操作,如果,不增加这句,前面就算实现了对应的方法,也是不会执行的
}

每种手势操作都提供对应的方法让我们处理对应的事件。例如,UIPanGestureRecognizer就提供了三种方法,如:

    • (CGPoint)translationInView:(UIView )aView; - (CGPoint)velocityInView:(UIView )aView; - (void)setTranslation:(CGPoint)translation inView:(UIView *)aView;

另外,我们就是通过对其state的判断来觉得该处理什么的样的操作了.例如,下面的判断就是说,当我们接触移动,或者停止接触的时候,我们都更新view

1
2
3
4
5
 if ((gesture.state == UIGestureRecognizerStateChanged) ||
        (gesture.state == UIGestureRecognizerStateEnded)) {
  CGPoint translation = [recognizer translationInView:self];
  self.origin = CGPointMake(self.origin.x+translation.x, self.origin.y+translation.y);         [recognizer setTranslation:CGPointZero inView:self];
 }

其它的一些手势操作

  • UIPinchGestureRecognizer
  • UIRotationGestureRecognizer
  • UISwipeGestureRecognizer
  • UITapGestureRecognizer

How-to-draw-circle

这里介绍,如何实现在UIView中画圆,下面是主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-(void)drawCircleAtPoint:(CGPoint)p withRadius:(CGFloat)radius inContext:(CGContextRef)context
{
    UIGraphicsPushContext(context);
    CGContextBeginPath(context);
    CGContextAddArc(context, p.x, p.y, radius, 0, 2*M_PI, YES);
    CGContextStrokePath(context);
    UIGraphicsPopContext();
}

- (void)drawRect:(CGRect)rect
{
    // Drawing code
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGPoint midPoint;
    midPoint.x = self.bounds.origin.x + self.bounds.size.width / 2 ;
    midPoint.y = self.bounds.origin.y + self.bounds.size.height / 2 ;
    CGFloat size = self.bounds.size.width / 2;
    if (self.bounds.size.height < self.bounds.size.width) size = self.bounds.size.height / 2 ;

    CGContextSetLineWidth(context, 5.0);
    [[UIColor redColor] setStroke];
    [self drawCircleAtPoint:midPoint withRadius:size inContext:context];

显示如下:

image

1
2
3
4
5
6
7
8
9
10
11
12
//Begin the path
CGContextBeginPath(context);

//Move around, add lines or arcs to the path
CGContextMoveToPoint(context, 75, 10);
CGContextAddLineToPoint(context, 160, 150);
CGContextAddLineToPoint(context, 10, 150);
//Close the path (connects the last point back to the first) CGContextClosePath(context); // not strictly required

//Actually the above draws nothing (yet)!
//You have to set the graphics state and then fill/stroke the above path to see anything. [[UIColor greenColor] setFill]; // object-oriented convenience method (more in a moment) [[UIColor redColor] setStroke];
CGContextDrawPath(context,kCGPathFillStroke); //kCGPathFillStrokeisaconstant

参考资料

画图代码笔记

GCD介绍

GCD是ios 4.0引入用于在应用程序中管理多个任务执行的技术。是C API的一部分。一般我们将其替代作为应用程序的多线程处理。 GCD的函数一般以”dispatch_”开头,我们可以使用这个执行某些后台任务,然后返回处理主线程的代码。

例子

下面介绍一个例子,创建gcd队列下载图片:

主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)viewWillAppear:(BOOL)animated
{
  tch_queue_t downloadQueue = dispatch_queue_create(image downloader, NULL);
  dispatch_async(downloadQueue, ^{
      NSData *imageData = [NSData dataWithContentsOfURL:networkURL];
      //UIKit使用只能在main thread中
      dispatch_async(dispatch_get_main_queue(), ^{
        UIImage *image = [UIImage imageWithData:imageData];
        self.imageView.image = image;
        self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
        self.scrollView.contentSize = image.size;
        });
      });

  dispatch_release(downloadQueue);
}

参考资料

GCD

Build-oauth2-provider

introduce

最近,花了一段时间去整理oauth2.0 provider的功能,现在,很多互联网都是通过此方式共享API接口, 例如,豆瓣,新浪等等。其特点是不需要用户在第三方应用中输入用户名、密码即可完成授权操作。现在最新版本是 draft-ietf-oauth-v2-22,具体流程可参考其文档。这里就不赘述其细节了,简单来说就是通过client_id和client_secret还有redirect_uri这几个参数,请求验证服务器的授权,然后通过返回一个可信任的access_token来调用服务器的资源。下面,介绍一下如何在rails 中实现这个流程。

实现

在github上查找了几个gem, 最终选定 oauth2-provider ,下面介绍一下实现方式。

首先,我们创建一个oauth_controller,添加下面代码

oauth_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class OauthController < ApplicationController
  def authorize # 返回authorize_code
    @oauth2 = OAuth2::Provider.parse(current_user.shop, request)
    #response.headers = @oauth2.response_headers
    #response.status = @oauth2.response_status
    redirect_to @oauth2.redirect_uri  and return if @oauth2.redirect?
  end

  def access_token # 返回access_token,记得返回值需为json
    @oauth2 = OAuth2::Provider.parse(nil, request)
    render json: @oauth2.response_body
  end

  def allow  #用户授权
    @auth = OAuth2::Provider::Authorization.new(current_user, params)
    if params['allow'] == '1'
      @auth.grant_access!
    else
      @auth.deny_access!
    end
    redirect_to @auth.redirect_uri
  end
end

在config/routes.rb中添加下面路由

config/routes.rb
1
2
3
 get '/oauth/authorize'    , to: 'oauth#authorize'   , as: :authorize
 post '/oauth/access_token', to: 'oauth#access_token', as: :access_token
 match '/oauth/allow'      , to: 'oauth#allow'       , as: :oauth_allow

现在来说,基本流程就定义清楚了。

这时用户已经能通过如下方式请求授权了

client_demo.rb
1
2
3
4
5
6
7
8
9
10
require 'oauth2'
client = OAuth2::Client.new('client_id', 'client_secret', :site => 'https://example.org')

client.auth_code.authorize_url(:redirect_uri => 'http://localhost:8080/oauth2/callback')
# => "https://example.org/oauth/authorization?response_type=code&client_id=client_id&redirect_uri=http://localhost:8080/oauth2/callback"

token = client.auth_code.get_token('authorization_code_value', :redirect_uri => 'http://localhost:8080/oauth2/callback')
response = token.get('/api/resource', :params => { 'query_foo' => 'bar' })
response.class.name
# => OAuth2::Response

过滤器

当然,这里在我们服务器上得加个过滤器,去验证用户是否是正确的。

application_controller.rb
1
2
3
4
5
6
7
8
9
10
  def login_or_oauth_required
    unless session[:shop]
      token = OAuth2::Provider.access_token(nil, [], request)
        unless token.valid?
          render json: {error: '[API] Invalid API key or permission token (unrecognized login or wrong password)'}
        else
          session[:shop] ||= token.owner.as_json(only: [:deadline, :created_at, :updated_at, :name])['shop']
        end
     end
  end

参考资料

github-oauth2 oauth2中文 oauth2-spec

后台管理框架active_admin

简介

基于rails的管理框架还是很多的,例如,rails_admin, active_admin等。 这里,介绍下最近使用的 active_admin

安装

在gemfile中添加

1
2
  gem 'activeadmin'
  gem 'sass-rails' #only in rails 3.1

装完对应的gem之后,执行下面task

1
   rails generate active_admin:install

这里会创建对应的文件,主要是在routes中添加对应的路由,和active_admin的设置文件,还有,就是 创建了对应的active_admin model,和对应的database migrate,在migrate中,创建了初始化的active_admin 的记录admin@example.com,我们可以通过这个登录后台管理的页面。当然,在执行数据迁徙前,我们可以将 这个改成我们所希望创建的默认用户。

使用

active_admin 创建的文件都放在app/admin这个目录下面。我们要想自定义首页显示的内容,可以查看dashboard文件, 更改里面的内容就能直观的显示在首页上了。 如果,我们原来使用的控制器,定义了module Admin里,我们可以通过更改 config/initializers/active_admin.rb这里面的配置,将config.default_namespace = :admin,换成我们所想要的,防止冲突。

当我们要对某个表进行管理时,我们此时就可以通过创建对应的active_admin resource,执行下面命令,就可以创建对应的ActiveAdmin model了. 执行完这个之后,在我们的后台中,会自动创建对应的增删改查功能。同样,我们可以自定义显示它,具体可以查看 资料

参考资料

github gem demo doc info

将rails项目升级到rails 3.1

有很久都没更新博客了,最近,发现很多东西不记下来,特别容易忘记。最近把项目从rails 3.0迁移 到了3.1版本,rails 3.1增加了许多新的特性,比如说:默认支持coffee-script,把jQuery作为默认的js库 等等,还有一些新特性,可以查看 rails 3.1 release note .

更新gem

比如说把原来的meta_where替换成squeel等等,基本上,现在版本的gem都支持rails 3.1了, 另外,原来的用于编译 coffeescript的barista可以去掉了,因为默认rails模版已经支持编译coffeescript了。以前,默认的情况下,我们设置 base=true这个配置,用来不让

1
2
3
  (function() {
    ...
    }).call(this);

包围js代码,这样很多js变量就能全局性的用了。但是,现在rails ,默认是将bare设置为false的,这个可以看 rails的源码 .在这种情况下,我们可以通过设置gloabla 变量,如window.xxx = xxx . 这里有 一些讨论 , 另外还有就是active_hash这个gem也做了一些改变, 当我们要实现activerecord和activehash对象关联时,得将原来的代码改写成:

1
2
3
4
5
6
7
class Country < ActiveHash::Base
end

class Person < ActiveRecord::Base
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :country
end

sprockets , asset pipline

这个应该是rails 3.1最主要的更新内容了。用asset pipline 替换了原有的?形式的引入文件, 利用的是 sprockets gem .这个具体可以看 文章 ,这里面详细介绍了,用最新hash方式缓存静态文件。

subdomain, domain

现在rails url helper已经默认支持subdomain了,我们可以使用xxx_url(subdomian: xxx)

Http Steaming

HTTP Streaming是Rails 3.1中一项新改进,可以让浏览器在页面作出响应的同时下载样式表和JavaScript文件。该特性需要Ruby 1.9.2,以及Web服务器的支持,幸运的是流行的nginx和unicorn组合已经支持。

一些方法

在rails 3.1 的更新日志中,你会发现,clone方法发生了改变,我们以前用clone方法复制model,会是以下这种情况

1
2
  p = Product.first.clone
  p.new_record? # => true

而现在clone 之后的对象调用new_record?会返回false,即这是一种浅赋值,以下是changelog内容

1. ActiveRecord::Base#dup and ActiveRecord::Base#clone semantics have changed to closer match normal Ruby dup and clone semantics.

1. Calling ActiveRecord::Base#clone will result in a shallow copy of the record, including copying the frozen state. No callbacks will be called.

1. Calling ActiveRecord::Base#dup will duplicate the record, including calling after initialize hooks. Frozen state will not be copied, and all associations will be cleared. A duped record will return true for new_record?, have a nil id field, and is saveable.

另外,现在在query的时候,我们不能写以前的那种关联方式查询了,例如下面例子

1
2
3
   Shop.where(user: User.first)
   #上面的话得换成
   Shop.where(user_id: User.first)

还有,现在通过association.new创建对象,等于以前的build方式创建对象了。

Rails3中邮件服务设置

对于一个web服务,邮件提醒是必须的,比如,发送给用户注册信息,密码重置信息等等, 都需要通过邮件来提醒用户。这里简单介绍一下rails3的邮件服务设置。

首先,我们得创建mailer

    rails g mailer UserMailer
  

接着,我们编辑app/mailer/user_mailer

1
2
3
4
5
6
7
UserMailer < ActionMailer::Base
 default :from => "xxx@gmail.com"

   def welcome
     mail(to: "xxx@gmail.com",body:"aaa", subject: "welcome")
   end
 end

下一步,就是配置action mailer了,由于国内的gmail连接老出现问题,所以这里就介绍163的了, 基本都差不多

1
2
3
4
5
6
7
8
9
10
11
 ActionMailer::Base.smtp_settings = {
   :address              => "smtp.163.com",
   :port                 =>  25,
   :domain               => "domail.com",
   :user_name            => "xxx@163.com",
   :password             => "xxx"
   :authentication       => "plain",
   #if got the same error with me,please change the options false
   :enable_starttls_auto => true
  }
ActionMailer::Base.default_url_options[:host] = "localhost:3000"

注意:如果你和我出现了同样的堆栈错误

这样,我们在rails console中执行 UserMailer.welcome.deliver就能发送邮件到指定的 邮箱了。

参考资料

mail guides action mailer in rails 3