Archive

Author Archive

使用dsHistory让js方法也能前进后退

April 14th, 2011 yakjuly No comments

在网站开发中 一个好的用户体验 离不开优秀的设计和强大的javascript。

锚点和js 是前端开发工程师的强大利器。通过这把利器 gmail 让邮件浏览速度变的更快,github也让issue浏览速度变的更快,twitter在url中添加 “#!”来改善用户体验。

dsHistory 是一个非常小 跨浏览器的javascript库,它可以在网页程序中控制前进,后退事件。

它的实现原理是 给当前网页上加锚点信息,同时可以让一个js方法绑定锚点信息,前进或后退 触发锚点的改变时调用绑定的js方法。

下面我用需求和代码来描述它的作用吧。

需求:

  • 我有一个课程有一连串的题目 让用户去完成,每个题目类型都不一样,每次页面只出现一个题目,
  • 我不希望 用户每次点击下一题 或者 选择 一题的答案的时候刷新页面 那样太慢了。
  • 我希望通过一个url能够直接定位道某个课程的某道题目上。
  • 我希望用户可以通过浏览器的 前进,后退来切换 下一题 和 上一题。

解决:

我可以让用户每次的点击产生ajax请求,刷新局部页面。但是每道题目都不同,有的需要播放视频 有的选择结果需要产生特效等 那么事实上还是需要在局部刷新后执行js。那还不如服务器端提供json数据 让js来完成所有的动作呢。需要一个特定的url能直接访问到某题 这个简单每个题目的url唯一就行了,让前进 后退可以切换题目 要求功能不坏,还不让刷新页面 天啊 只能用锚点了 + js完成这些功能了。

于是我开始写代码

1.定义一个Lesson

var Lesson = function(container, data, target)
  var self = this;
  this.container = container;
  this.id = data.id;
  this.root_screen_id = data.root_screen_id;
 
  if (target) {
    this.gotoScreen(target)
  } else {
    this.gotoScreen(this.root_screen_id);
  }
}

2.定义跳转到题目的方法

Lesson.prototype = {
  gotoScreen : function(id){
    this._loadScreenData(id);
  },

  _loadScreenData: function(id){
    var engine = this;
    $.getJSON("/get_screen_data?id="+id, {}, function(data, textStatus){
      engine._setDsHistoryAndBuildScreen(data, data.id);
    })
  },

  _setDsHistoryAndBuildScreen: function(data, screen){
    var engine = this;
    var hadOverride = dsHistory.QueryElements["screen"] != null;
    dsHistory.setQueryVar("screen", String(screen));
    var historyFunction = function() {
        engine._buildScreen(data, screen);
    };
    if (hadOverride) {
      dsHistory.bindQueryVars(historyFunction);
    }
    else {
      dsHistory.addFunction(historyFunction);
    }
    engine._buildScreen(data, screen);
  },

  _buildScreen: function(data, screen){
    // replace something in page and bind element with function
  }
}

我把题目的跳转定义在一个方法里gotoScreen,每次调用gotoScreen就会去后台读数据 并刷新页面 绑定事件等。

在读完数据后 我并没有急着替换页面元素 而是让dshistory设置锚点信息,并把当前锚点信息绑定好方法_buildScreen(data, screen)。这样在前进后退时如果有锚点信息和当前的screen相同 则会触发_buildScreen方法。

3.剩下的就是创建lesson

<script type="text/javascript">
  var global = this;
  $(document).ready(function(){
    global.lesson = new Lesson($(".activity"),
                              {id: <%= @lesson.id %>, root_screen_id: <%= @lesson.root_script_screen.id %>},
                              dsHistory.QueryElements["screen"])
  });
</script>

awesome!我把需求都完成了 而且代码非常简单,我甚至在这段代码中 看到了一点框架的东西  如果结合上ejs 那就是javascript mvc了。

 

 


 

Categories: javascript Tags:

半天搞定capistrano部署rails3至dreamhost

March 30th, 2011 yakjuly No comments

以前在公司部署都是用webistrano,图形化界面,部署代码也是sonic写,自己都不带操心的。部署得练练,不然被人一问三不知,可是很羞愧的。

首先做了一个简单的rails3 app:cookbook代码上传至github

在Gemfile中添加 gem ‘capistrano’

读capistarno的wiki getting start

安装完后要准备一些事情:

  • 必须使用ssh访问你的服务器
  • 远程服务器必须安装了POSIX-compatible shell,“sh”命令必须在默认的系统环境中
  • 将你电脑中的public key放在服务器上,保证你不用输入密码来登陆服务器

前两样基本都能满足,第三个 需要你 在终端执行命令

ssh-keygen -t rsa

系统会提示输入passphrase 此处不填,两次回车会在 ~/.ssh 下创建两个文件 id_rsa 和id_rsa.pub。文件用途如下
~/.ssh/id_rsa

  • 该用户默认的 RSA 身份认证私钥(SSH-2)。此文件的权限应当至少限制为”600″。
  • 生成密钥的时候可以指定采用密语来加密该私钥(3DES)。
  • ssh(1) 将在登录的时候读取这个文件。

~/.ssh/id_rsa.pub

  • 该用户默认的 RSA 身份认证公钥(SSH-2)。此文件无需保密。
  • 此文件的内容应该添加到所有 RSA 目标主机的 ~/.ssh/authorized_keys 文件中。

密钥和公钥创建后把公钥传到要部署的服务器上

scp ~/.ssh/id_rsa.pub yakjuly@yakjuly.com:~/.ssh/authrozied_keys

以后你从本地电脑ssh登陆服务器 就不用输入密码了,系统会根据密钥和公钥对你的身份进行认证。
同理 你把公钥 写到github帐户信息的public sshkeys中 根据 git@github.com:/xxxx/xx.git 下载时 也不用输入密码了。
因为我用github管理代码 因此 服务器上也需要生成一套密钥,以便于下载代码不需要输入密码, 把公钥填到github帐户中,这里有教程

准备好后 先按照capistrano wiki上的小例子,练下手。

在项目root下创建一个capfile文件,

task :search_libs, :hosts => "yakjuly@www.yakjuly.com" do
  run "ls -x1 /usr/lib | grep -i xml"
end

运行

cap search_libs

显示结果

* executing `search_libs'
 * executing "ls -x1 /usr/lib | grep -i xml"
 servers: ["www.yakjuly.com"]
 [yakjuly@www.yakjuly.com] executing command
 ** [out :: yakjuly@www.yakjuly.com] libwx_baseu_xml-2.6.so.0
 ** [out :: yakjuly@www.yakjuly.com] libwx_baseu_xml-2.6.so.0.3.1
 ** [out :: yakjuly@www.yakjuly.com] libxml2.a
 ** [out :: yakjuly@www.yakjuly.com] libxml2.la
 ** [out :: yakjuly@www.yakjuly.com] libxml2.so
 ** [out :: yakjuly@www.yakjuly.com] libxml2.so.2
 ** [out :: yakjuly@www.yakjuly.com] libxml2.so.2.6.32
 ** [out :: yakjuly@www.yakjuly.com] libxmlparse.a
 ** [out :: yakjuly@www.yakjuly.com] libxmlparse.so
 ** [out :: yakjuly@www.yakjuly.com] libxmlparse.so.1
 ** [out :: yakjuly@www.yakjuly.com] libxmlparse.so.1.2
 ** [out :: yakjuly@www.yakjuly.com] libxmltok.a
 ** [out :: yakjuly@www.yakjuly.com] libxmltok.so
 ** [out :: yakjuly@www.yakjuly.com] libxmltok.so.1
 ** [out :: yakjuly@www.yakjuly.com] libxmltok.so.1.2
 ** [out :: yakjuly@www.yakjuly.com] xml2Conf.sh
 command finished in 560ms

看日志就能知道了大概原理了:本地写好部署代码 远程执行 部署 返回日志 结果。

接着看Getting Start,接着写了 role ,set 设置跳板机 gateway,部署多个域名 等方法。 后面还讲了 cap invoke 和cap shell。
当我按照教程执行 cap -T的时候,结果没有出现下面那么多tasks

cap deploy               # Deploys your project.
cap deploy:check         # Test deployment dependencies.
cap deploy:cleanup       # Clean up old releases.
cap deploy:cold          # Deploys and starts a `cold' application.
cap deploy:migrate       # Run the migrate rake task.
cap deploy:migrations    # Deploy and run pending migrations.
cap deploy:pending       # Displays the commits since your last deploy.

原因是没有在app的root_path中执行 capify .
这个方法会在app根目录下创建 Capfile和 config/deploy.rb
cap默认的任务列表就可以在这个对这个app执行了

接下来 编辑 config/deploy.rb。 文件中的描述信息很清楚 跟着提示 填内容就行

set :application, "cookbook"
set :repository,  "git@github.com:yakjuly/cookbook_example.git"
set :user, "yakjuly"
set :scm, "git"
set :scm_verbose, true
set :branch, "master"
set :deploy_to, "/home/yakjuly/cookbook.yakjuly.com"
set :use_sudo, false

role :web, "yakjuly.com"
role :app, "yakjuly.com"

dreamhost没有sudo权限,这里要把use_sudo设置为false。
dreamhost的mysql数据库只能在cpannel中创建和访问 所以这里也省略role :db

第一次部署最好执行下

cap deploy:setup

会在服务器上创建shared, releases, current文件夹

  • release下保存着每次不同代码部署版本的文件夹,例如下面会有 20110330040708  20110330041621  20110330042212  20110330082446
  • current指向的是最新的releases的代码  current -> /home/yakjuly/cookbook.yakjuly.com/releases/20110330082446
  • shared是每个版本共享的log等文件存放的文件夹

例如:
current下的log文件夹
lrwxrwxrwx 1 yakjuly pg2609356   45 2011-03-30 01:24 log -> /home/yakjuly/cookbook.yakjuly.com/shared/log

 

这些都做了解后,开始部署吧

cap deploy

日志显示代码部署部分都正常 但是在 `deploy:restart’ 时候出错,因为dreamhost重新启动passenger是通过 touch tmp/restart.txt来触发的。
因此需要修改deploy:restart 添加代码到Capfile最后

namespace :deploy do
  desc "Restarting after deployment"
  task :restart, :roles => :app do
    run "cd #{release_path} && touch tmp/restart.txt"
  end
end

由于服务器上某些配置文件和开发环境不同,可以把修改配置文件的部署写在Capfile或者deploy.rb中

desc "change database etc"
task :link_symlink, :roles => :app do
  %w(database).each do |config|
    run "cd #{release_path} && rm -rf config/#{config}.yml && ln -sf ../../../shared/config/#{config}.yml config/"
  end
end

desc "set environment after code update."
task :set_environment, :roles => :app do
  run "sed 's/# ENV\\[/ENV\\[/g' #{release_path}/config/environment.rb > #{release_path}/config/environment.temp"
  run "mv #{release_path}/config/environment.temp #{release_path}/config/environment.rb"
end

after "deploy:update_code", :link_symlink, :set_environment

写到这里基本上大功告成了。
但是有个小问题需要注意,部署到服务器上的bundle install能够执行顺利,
但是passenger却爆xxx插件没有安装请运行 bundle install,是因为passenger没有找到bundle安装插件的path
需要在app的root目录下 添加 .bundle/config

---
BUNDLE_DISABLE_SHARED_GEMS: "1"
BUNDLE_PATH: /home/yakjuly/.bundler

运行一遍试试吧,部署是不是很简单?

servers: ["yakjuly.com"]
    [yakjuly.com] executing command
 ** [yakjuly.com :: err] From github.com:yakjuly/cookbook_example
 ** 4065eb6..459fc2e  master     -> origin/master
 ** [yakjuly.com :: out] HEAD is now at 459fc2e change title
    command finished in 1965ms
    copying the cached version to /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501
  * executing "cp -RPp /home/yakjuly/cookbook.yakjuly.com/shared/cached-copy /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501 && (echo 459fc2edd6fdbf48458ebebe27a18d776cf65182 > /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/REVISION)"
    servers: ["yakjuly.com"]
    [yakjuly.com] executing command
    command finished in 572ms
  * executing `deploy:finalize_update'
  * executing "chmod -R g+w /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501"
    servers: ["yakjuly.com"]
    [yakjuly.com] executing command
    command finished in 550ms
  * executing "rm -rf /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/log /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/public/system /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/tmp/pids &&\\\n      mkdir -p /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/public &&\\\n      mkdir -p /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/tmp &&\\\n      ln -s /home/yakjuly/cookbook.yakjuly.com/shared/log /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/log &&\\\n      ln -s /home/yakjuly/cookbook.yakjuly.com/shared/system /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/public/system &&\\\n      ln -s /home/yakjuly/cookbook.yakjuly.com/shared/pids /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/tmp/pids"
    servers: ["yakjuly.com"]
    [yakjuly.com] executing command
    command finished in 562ms
  * executing "find /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/public/images /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/public/stylesheets /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/public/javascripts -exec touch -t 201103300915.02 {} ';'; true"
    servers: ["yakjuly.com"]
    [yakjuly.com] executing command
    command finished in 618ms
    triggering after callbacks for `deploy:update_code'
  * executing `link_symlink'
  * executing "cd /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501 && rm -rf config/database.yml && ln -sf ../../../shared/config/database.yml config/"
    servers: ["yakjuly.com"]
    [yakjuly.com] executing command
    command finished in 549ms
  * executing `set_environment'
  * executing "sed 's/# ENV\\[/ENV\\[/g' /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/config/environment.rb > /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/config/environment.temp"
    servers: ["yakjuly.com"]
    [yakjuly.com] executing command
    command finished in 548ms
  * executing "mv /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/config/environment.temp /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501/config/environment.rb"
    servers: ["yakjuly.com"]
    [yakjuly.com] executing command
    command finished in 602ms
  * executing `deploy:symlink'
  * executing "rm -f /home/yakjuly/cookbook.yakjuly.com/current && ln -s /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501 /home/yakjuly/cookbook.yakjuly.com/current"
    servers: ["yakjuly.com"]
    [yakjuly.com] executing command
    command finished in 550ms
 ** transaction: commit
  * executing `deploy:restart'
  * executing "cd /home/yakjuly/cookbook.yakjuly.com/releases/20110330091501 && touch tmp/restart.txt"
    servers: ["yakjuly.com"]
    [yakjuly.com] executing command
    command finished in 548ms
Categories: dreamhost, linux, rails Tags:

补习metaclass

March 29th, 2011 yakjuly No comments
1. class << A 是什么意思?
2. class << self 又是怎样的呢?
1. 首先,Ruby中创建(定义)一个类有两种方式:
a) 定义一个(适合于)普通的类
class ClassName
   body
end
b) 为某一个具体的对象定义其特定的类(singleton class, 或者叫metaclass,类的singleton class就是metaclass?)
class << object                    # 这里define 的就是singleton class
   body                             # body里要定义 的method 就是 singleton method
end
而这个例子就是第二种情况 – 事实上,这是在定义一个class A 的singleton class,而这个singleton class的作用就是存放对class A有意义的class method。
需要插一句的是,这种做法的存在又基于: in Ruby, a class is just a instance of class Class。所以,我们不仅仅可以为object来定义针对它自身的类,还可以为class来定义针对它自身的类 – 因为它们其实都是某个类的instance。
虽然这里定义的是class method,但是由于scope是在class A这个实例的class里面,所以我们事实上是在定义这个singleton class的instance method。
所以,这里的method name 是不需要class name 为前缀的。
事实上,这好比:
def A.[classMethodName_1]
   method body here
end
def A.[classMethodName_2]
  method body here
end
又或者是
def self.[classMethodName]
  method body here
end
2. 这个要看context,就是看这个self的作用域在哪里 – 具体就是这个语句在什么地方出现。
而一般来说,举例会有:
 

class A
   class << self
     def methodForA
        puts "singleton method(class method) for A"
     end
  end
end

 

而这个是完全等同于:
 

class A
   def self.methodForA
     puts "singleton method(class method) for A"
  end
end

 

谁先定义,谁被覆盖,谁后定义,谁起作用。
class A
  puts self
  puts(class << self; self; end)
end

# => A
# => # <Class:A>

As it is cleared from the output that inside the class A, self is reflecting the class A itself whereas class << self; self; end is <Class:A>, so what is the exact difference ?
Lets exemplify it…

class A

  self.module_eval do
    define_method :wish do
      puts “hello instance method”
    end
  end

  (class << self; self; end).module_eval do
    define_method :wish do
      puts “hello class method”
    end
  end

end

A.wish # => hello class method
A.new.wish # => hello instance method

class B

  self.module_eval do
    define_method :wish do
      puts “hello instance method”
    end
  end

end

B.new.wish # => hello instance method
B.wish # => undefined method `wish’ for B:Class (NoMethodError)

I suppose that it is clear now, in case if it is not than look at this… The following code will do exactly same as above but the class method wish will be available to all classes or we can say to all instances of class Class

class Class

  self.module_eval do
    define_method :wish do
      puts “hello — this is class method for instance of class Class”
    end
  end

end

class A

  self.module_eval do
    define_method :wish do
      puts “hello instance method”
    end
  end

end

A.wish # => hello — this is class method for instance of class Class
A.new.wish # => hello instance method

class B

  self.module_eval do
    define_method :wish do
      puts “hello instance method”
    end
  end

end

B.wish # => hello — this is class method for instance of class Class
B.new.wish # => hello instance method
Categories: ruby Tags:

nodeJS 事件驱动模型 多线程

March 29th, 2011 yakjuly No comments

这几天一直关注nodeJS,看了很多关于nodejs的评论 以及example代码。

十几篇博文看下来 我的脑袋里突然多了很多东西,记下来 继续深入。

现在http不能完全满足 浏览器与服务器的交互,常用的方法 1.长轮询 2.websocket 3.flash socket

虽然现在html5在移动设备上很火,但是在pc端还没有攻占下来。

flash socket属于hack的一种 不算正道

长轮询,客户端始终发一个请求到服务器端,服务器端有内容则立即返回,没有内容则保持连接 不返回结果,等到有内容时再返回结果,客户端得到结果后再发请求。 这样客户端与服务器端就一直保持着一个连接,可以用来做web IM, web game 等

但是 这种长轮询 无论在客户端有没有需要得到内容得时候都需要和服务器端保持一个空连接,大量的并发和连接 服务器是一大考验。

apache vs nginx: 基于线程模型 vs 事件驱动模型

稍微回顾一下请求Request 这一环节的过程。现今多数的Web 服务器中,有一条新的链接就会申请一条线程来负责处理至到这个Request 周期结束,接着执行其他流程。可以想象,成千上万个链接便有成千上万条线程(Thread-spawning )。每条线程姑且以堆栈2MB 的消耗去计算,一条条线程它们的累加都是不小的数目。如何优化和改进本身就是一个大问题,此外,使用系统线程,必须考虑线程锁的问题,否则造成堵塞主进程又是一个令人操心的难题。

nodejs的优势 正是应对长轮询的压力,nodejs的优势的原因是采用 事件驱动模型 ,什么是事件驱动模型呢?

总之不是直接的某个函数method()去执行(那是同步的方式,Node.js也支持),而是写回调callback

例如:

function test(){

  var result = doSomething();
  ..do something else..
}

在这段代码中在doSomething的时候 result只能等待结果,等待后才能接下来执行程序。

function test(){
  $.ajax({
    url: xxx,
    success: doSomething
  })

  .. do something else
}

这段代码应该很熟悉,jquery调用ajax请求,这段代码中ajax请求通过传递一个callback作为参数,不需要等待请求结果返回再执行something else,这就是我理解的事件驱动模型。

事件驱动模型的特点:某些方法返回结果更快,程序的执行不再用blocking方式写。

在这里也顺便了解了下ruby1.9引入的新概念 Fiber

Fiber VS Thread

Ruby的Fiber是一种控制结构,它的运行方式和操作方法很像thread。

Fiber Thread
not concurrency concurrency
resume, suspend(call Fiber.yield或者等到block结束) resume, suspend
return and give control back to caller not return or give control back unless join it 

 

 

fib = Fiber.new do
   f1 = f2 = 1
   loop do
     Fiber.yield f1 (每次resume,在遇到Fiber.yield或者block结尾时结束)
     f1, f2 = f2, f1 + f2 (第二次resume,从这里开始)
   end
end  

10.times { puts fib.resume }

ruby-1.9.2-p136 > 10.times {puts fib.resume }
1
1
2
3
5
8
13
21
34
55

另一种实现方法

def local_proc
  f1 = f2 = 1
  return Proc.new {f1, f2 = f2, f1 + f2; p f1;}
end
proc1 = local_proc
10.times {proc1.call}

上 面两个方法的实现方式是很不相同的。Fiber方式通过 enable the automatic conversion of internal iterators, such as each, to enumerator or external iterators来实现。(就是允许转换为 内部的 循环序列)

 

参考资料:http://blog.csdn.net/zhangxin09/archive/2010/08/25/5836777.aspx

http://andyhu1007.javaeye.com/blog/634916

http://www.tbdata.org/archives/tag/nginx

http://pragdave.blogs.pragprog.com/pragdave/2007/12/pipelines-using.html

http://davidwalsh.name/websocket

https://github.com/joyent/node/wiki/Projects,-Applications,-and-Companies-Using-Node

Categories: javascript, ruby Tags:

swfupload_for_blade

March 19th, 2011 yakjuly No comments

rails的上传插件非常丰富,比较著名的有 attachment_fu, paperclip.

上传大文件时 需要显示进度 怎么办?办法有两种

 

1. 给nginx安装 nginx upload module 插件,并且使用 jquery-upload-progress 轮询nginx 并显示进度。

nginx upload module 插件能让nginx把上传的文件转移到服务器某个临时文件上,上传完毕后再给rails发一个请求。

优点:nginx完成了静态文件上传的动作,使rails进程只关注动态请求

该方案在 多层nginx映射的时候会有问题,出现 文件上传时 过一段时间 才能显示出进度,原因不明,而且轮询也会给nginx带来压力。

 

2.使用swfupload 在客户端显示上传进度。

缺点:需要rails进程处理文件上传

 

swfupload_for_blade

swfupload + paperclip,主要是使用rails g swfupload 快速生成 controller helper model view等代码,对不同的需求可以自己改代码 而不需要改插件。

使用方法:

Gemfile中添加代码

gem 'swfupload_for_blade'
gem 'paperclip'
gem 'mime-types', '1.16', :require => 'mime/types'

在application.rb中config的block中添加代码

config.autoload_paths += %W( #{config.root}/lib )
config.autoload_paths += %W( #{config.root}/app/middleware )

目的是加载attachable.rb 和 flash_sesson_cookie_middleware.rb

接着在config/initalizers/session_store.rb文件最后加上

Rails.application.config.middleware.insert_before(
  ActionDispatch::Session::CookieStore,
  FlashSessionCookieMiddleware,
  Rails.application.config.session_options[:key]
)

剩下的工作

rails g swfupload
rake db:migrate

class User < ActiveRecord::Base
  has_file
end

class Comment < ActiveRecord::Base
  has_files
end

in layout
<%= javascript_include_tag "jquery" %>

views
<%= swfupload_of(@user) %>
<%= swfupload_of(@comment) %>

目前已经更新 @user 在new 或者 created的情况下 一对一 ,一对多都能够上传文件。

Attachment表 需要定时删除没有关联的记录。

 

Categories: javascript, rails, ruby Tags: