Cantin's blog

......

Currying in Ruby

| Comments

Currying

Currying is the technique of transforming a function that takes multiple arguments (or a tuple of arguments) in such a way that it can be called as a chain of functions, each with a single argument .

For instance:

1
2
3
4
5
6
7
8
9
10
11
12
13
function add(x, y, z) {
  return x + y + z;
}

function add5(y, z) {
  return add(5, y, z);
}

function add5and6(z) {
  return add5(6, z);
}

add5and6(10) => 21

Currying in Proc of Ruby

In Ruby, Object is the first class, but we still can use it as FP.

Proc is kind of like function, Ruby provide a method named currying in Proc. This give us a easy way to currying proc like currying function.

Baisc Usage:

1
2
3
4
5
add = proc { |x, y, z| x + y + z }
add5 = add.curry[5]
add5and6 = add5.curry[6] # equal to add.curry.call(5).call(6)

add5and6.(10) => 21

Proc#curry also can take one argument(arity) that limit the quantity of arguments.

For instance:

1
2
3
4
add = proc { |x, y, z| x + y + (z || 0) }
add5 = proc.curry(2)[5]
add5and6 = add5.curry[6] => 11
# z will be ignored cause by the arity is 2

Note: the arity argument in Lambda#curry must equal to the number of arguments in Lambda, otherwise it will raise a ArgumentError.

Resize Disk in VirtualBox

| Comments

resize disk in virtualbox

首先我们要在VirtualBox里面扩容我们的镜像。 VirtualBox本身有自带的command tool,利用它们可以很方便的配置镜像。

进入你的镜像目录,找到.vdi文件,执行下列命令:

1
2
VBoxManage modifyhd Centos-64bit-disk.vdi --resize 18432
#18432 = 18 * 1024, resize to 18G

Note: 如果你的文件是.vmdk格式,那么需要先转换成.vdi, 然后替换掉原来的硬盘镜像。

1
VBoxManage clonehd Centos-64bit-disk1.vmdk Centos-64bit-disk1.vdi --format vdi

至此,你的disk image已经在VirtualBox中被扩展到18G,下一步就是要让Centos扩容。

apply resize in Centos

要在Centos里面扩容,首先要知道你的分区情况。

Centos 6.4默认的分区方式是两个: /dev/sda1挂载了/boot,大小约为500M, 剩余的硬盘都在/dev/sda2,由LVM统一管理。

LVM(Logical Volume Manager)建立在硬盘和分区之上的一个逻辑层,来提高磁盘分 区管理的灵活性.

默认的LVM有两个分区,一个是vg_livedvd-lv_root,数据都在这里面,另一个是vg_livedvd-lv_swap,用以缓存。

所以,在Centos扩容,首先要扩展分区容量,然后在扩展LVM的容量。

分区容量可以使用gparted来扩容。 将分区容量扩大后,就可以开始处理LVM。

打开虚拟机,输入命令:

1
2
3
4
lvextend -L15G /dev/mapper/vg_livedvd-lv_root
#extend vg_livedvd-lv_root to 15G

resize2fs vg_livedvd-lv_root

这样子vg_livedvd-lv_root就变成了15G大小了。

Same Origin Policy

| Comments

Same Origin Policy(同源策略)

同源策略是浏览器用来防止恶意javascript执行的策略。

同源策略意为:只有在同源下的JS对象才可以互相访问,操作。

源的意思是指js所在的页面,如test.html中通过jquery CDN引入的jQuery对象, 它的源是test.html,而不是script的src: ‘code.jquery.com’。

同源的意思是指拥有相同的协议,域名,端口。

JS对象的意思是所有BOM,DOM,以及所有的自定义对象。

例子:

我们在localhost:3000/test.html引入远程文件jQuery.js, 并再嵌入一个iframe, src为’127.0.0.1:3000/frame.html’。

1
2
3
4
5
6
7
8
9
10
11
12
<html>
  <body>
    <script src='http://code.jquery.com/jquery-1.8.3.js' type="text/javascript" charset="utf-8"></script>
    <script type="text/javascript" charset="utf-8">
      jQuery(function() {
        console.log(window.frames[0].location);
      });
    </script>

   <iframe src="127.0.0.1:3000/iframe.html" frameborder="0"></iframe>
  </body>
</html>

因为localhost与127.0.0.1在同源判断上是两个不同的源,所以 window.frames[0].location会报错,并且返回空的location

1
2
3
4
5
6
7
Warning:

Blocked a frame with origin "http://localhost:3000" from accessing a frame with origin "http://127.0.0.1:3000". Protocols, domains, and ports must match.

Return:

Location {}

Ruby Raise Exception

| Comments

Exception

Exception是ruby中代表异常的对象。它的实例方法有:

1
2
1.9.3p429 :002 > Exception.instance_methods(false)
 => [:exception, :==, :to_s, :message, :inspect, :backtrace, :set_backtrace]

其中:

message方法会调用to_s方法,用以返回exception的信息。

backtrace返回exception的调用栈,显示形式如下: `Filename: linenumber in methodname`

exception方法比较有趣,当没有参数或者是参数就是exception本身时,它会返回exception本身这个对象, 否则它会调用参数的to_str方法并创建一个新的exception对象。

1
2
3
4
5
6
7
8
9
10
11
12
  1.9.3p429 :003 > a = ArgumentError.new
   => #<ArgumentError: ArgumentError>
  1.9.3p429 :004 > a.object_id
   => 70171868342380
  1.9.3p429 :005 > a.exception.object_id
   => 70171868342380
  1.9.3p429 :006 > a.exception(a).object_id
   => 70171868342380
  1.9.3p429 :007 > b = a.exception('test')
   => #<ArgumentError: test>
  1.9.3p429 :008 > b.object_id
   => 70171868160920

exception方法是ruby与其他语言的异常处理不同之处。下文将会介绍。

Raise

Raise是Kernel的一个方法,用于抛出Exception。

raise有几种调用方式:

1.无参数调用

raise会产生一个新的RuntimeError对象抛出

1
2
3
4
1.9.3p429 :013 > raise
RuntimeError:
  from (irb):13
  from /Users/can/.rvm/rubies/ruby-1.9.3-p429/bin/irb:16:in `<main>'

2.传入一个参数

2.1参数为String

raise会将创建一个新的RuntimeError,并将string参数作为这个对象的message。

1
2
3
4
1.9.3p429 :017 > raise 'test'
RuntimeError: test
  from (irb):17
  from /Users/can/.rvm/rubies/ruby-1.9.3-p429/bin/irb:16:in `<main>'

2.2 参数为Exception Class

raise会调用其new方法,创建一个新的Exception对象

1
2
3
4
1.9.3p429 :018 > raise ArgumentError
ArgumentError: ArgumentError
  from (irb):18
  from /Users/can/.rvm/rubies/ruby-1.9.3-p429/bin/irb:16:in `<main>'

2.3 参数为Exception Object

raise会调用这个object的exception方法,返回exceptin方法生成的exception对象

1
2
3
4
5
6
7
8
9
10
11
12
13
class ArgumentError
  def exception(string = self)
    puts 'exception happen'

    super string
  end
end

raise ArgumentError.new

#output
exception happen
exception.rb:57:in `<main>': ArgumentError (ArgumentError)

3.传入2-3个参数

3.1 2个参数

在2.2-2.3中介绍了,如果第一个参数为Class的话会调用new,为Object的话会调用到exception

而第二个参数会被作为exception的message传入exception的new/exception方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class ArgumentError
  def initialize(string = '')
    puts 'Initialize happen'

    super string
  end
  def exception(string = self)
    puts 'exception happen'

    super string
  end
end

begin
  raise ArgumentError, 'test'
rescue => e
  puts e
end

begin
  raise ArgumentError.new, 'test'
rescue => e
  puts e
end

#output
Initialize happen
test
Initialize happen
exception happen
test

3.2 3个参数

第三个参数是指定backtrace, 也就是上面所说的调用栈

Rescue StandardError rather than Exception

rescue默认是处理StandardError。

StandardError是Exception的子类,表示的是在ruby程序中可预见的,比较普通的exception, 如ArgumentError之类的应该被ruby程序处理的异常。

Exception下面还有了其他错误类,如SystemExit, SystemStackError,这些代表了一些更底层,更严重的,恢复可能性更小的exception。 一般来说,ruby程序不应该试图处理这些错误。

当我们自定义Exception时,最好也是继承于StandardError。

而且,rescue时最好不要rescue Exception,因为它会捕捉到其他的如SystemExit这样的错误。

Install_apple_gcc42

| Comments

#

Install apple-gcc42 by homebrew,

#

#

1
2
brew tap homebrew/dupes
brew install apple-gcc42

if it is too slow, you can download it manually.

  1. looking for Formula by http://braumeister.org/, or

find the url in ‘https://github.com/Homebrew/homebrew-dupes/blob/master/apple-gcc42.rb’, it is ‘http://r.research.att.com/tools/gcc-42-5666.3-darwin11.pkg’, version is ‘4.2.1-5666.3’, maybe not the lastest as time pass by.

  1. puts this in /usr/local/Library/Caches/Homebrew/, run again.

ps: Sometimes it will appear errors like: ‘make: /usr/bin/gcc-4.2: No such file or directory’. The resolution is link apple-gcc42 to gcc-4.2

1
  sudo ln -s /usr/local/bin/gcc-4.2 /usr/bin/gcc-4.2

pps: if you run ‘brew install appe-gcc42’, it says counldn’t found any Formula named apple-gcc42

first, try brew tap homebrew/dupes, then run again.

seaond, download the ‘https://github.com/Homebrew/homebrew-dupes/blob/master/apple-gcc42.rb’, put it into /usr/local/Library/Formula/, then try again

#

Jquery-rails-monkey-patch

| Comments

Jquery-rails monkey patch

Since Rails 3, unconspicuous javascript(jquery-rails) instead of prototype.js to make thing easily. It provide a sample way to handle ajax, confirm, etc…

Sometimes jquery-rails couldn’t fit for our situation.For example:

With added “confirm: ‘this is is a confirm’”, the button element has the confirm function. But We could’t dynamically set the text whlie the confirm appear.

In this case, We can add a monkey patch into jquery-rails to fit for our need.

First of all We look at the source of juqery-rails, figure out what make this happened.

1
2
3
4
5
6
7
8
9
10
11
  allowAction: function(element) {
    var message = element.data('confirm'),
        answer = false, callback;
    if (!message) { return true; }

    if (rails.fire(element, 'confirm')) {
      answer = rails.confirm(message);
      callback = rails.fire(element, 'confirm:complete', [answer]);
    }
    return answer && callback;
  },

After that, We overwrite this function in application.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  $.rails.allowAction = function(element) {
    var rails = $.rails;

    var message = element.data('confirm'),
        answer = false, callback;
    if (!message) { return true; }

    if (rails.fire(element, 'confirm')) {
      message = element.data('confirm');
      answer = rails.confirm(message);
      callback = rails.fire(element, 'confirm:complete', [answer]);
    }
    return answer && callback;
  }

Finally, We can dynamically set the confirm text.

Delayed-job

| Comments

delayed-job

delayed-job是一个开源的延时执行任务的后台程序,在web应用上主要可以用来 异步发送邮件,异步更新数据等等

requirement

delayed_job是用数据库来记录所要执行的异步任务的,所以他需要配合sql_db与onsql_db一起使用。

work with activerecord

delayed_job默认是与activerecord在一起使用的

安装:

1
  gem 'delayed_job'

generate:

1
2
rails generate delayed_job:active_record
rake db:migrate

work with mongoid

如果要跟mongoid配合使用的话,可以安装以下这个

1
 gem 'delayed_job_mongoid'

create_index:

1
script/rails runner 'Delayed::Backend::Mongoid::Job.create_indexes'

usage

invoke

普通调用delayed_job的方法是在调用对象的delay方法

1
@user.dealy.send_email

这样子delayed_job就会在后台异步处理send_email这个动作了

另外,也可以在class中定义handle_asynchronously来标明方法是否异步

1
2
3
4
5
class User
  def show
  end
  handle_asynchronously :show
end

这样调用show的时候就会进入后台处理。 另外handle_asynchronously还有几个参数,如:priority,:run_at :priority指明了这个任务的优先级别 :run_at指明了这个任务在什么时候执行

queues

delayed_job还可以指明这个任务是在那个queue里面执行,后面将介绍可以启动多个jobs来执行不同的queues

1
2
3
4
5
object.delay(:queue => 'tracking').method

Delayed::Job.enqueue job, :queue => 'tracking'

handle_asynchronously :tweet_later, :queue => 'tweets'

这样可以指明这个任务是在那个queue里面

running jobs

我们需要启动jobs才可以执行后台任务

1
rake jobs:work

或者

1
QUEUE=tracking rake jobs:work

另外,如果想以daemons的形式启动job

可以加上daemons这个gem

1
gem 'daemons'

generate:

1
rails generate delayed_job

然后执行

1
RAILS_ENV=production script/delayed_job start

这样就在production环境下启动job了

另外还有其他选项如下

1
2
3
4
5
6
7
8
RAILS_ENV=production script/delayed_job stop

RAILS_ENV=production script/delayed_job -n 2 start
//启动两个job

//启动queues
RAILS_ENV=production script/delayed_job --queue=tracking start
RAILS_ENV=production script/delayed_job --queues=mailers,tasks start

callback

delayed_job还有call_back可以在任务调用过程中对其进行操控

config

我们可以在config/initializers/delayed_job_config.rb中配置如下:

1
2
3
4
5
6
Delayed::Worker.destroy_failed_jobs = false
Delayed::Worker.sleep_delay = 60
Delayed::Worker.max_attempts = 3
Delayed::Worker.max_run_time = 5.minutes
Delayed::Worker.read_ahead = 10
Delayed::Worker.delay_jobs = !Rails.env.test?

work with devise

delayed_job一般跟devise配合是用来异步发送邮件,这里可以使用一个gem 叫’devise-async’, 可以跟不同的background program一起运作

但是这里要介绍一种hack的方法,就是使用handle_asynchronously 在config/initializers/delayed_job_config.rb中加入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module Devise
  module Models
    module Confirmable
      handle_asynchronously :send_confirmation_instructions
    end

    module Recoverable
      handle_asynchronously :send_reset_password_instructions
    end

    module Lockable
      handle_asynchronously :send_unlock_instructions
    end
  end
end

这样子更改了devise发送邮件的处理方式,从而使邮件异步发送。

work with exception_notification

当delayed_job处理任务失败,我们想让他发送一封邮件提醒我们出错了,这时候就可以使用 exception_notification

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# In config/environments/production.rb or config/initializers/delayed_job.rb

# Optional but recommended for less future surprises.
# Fail at startup if method does not exist instead of later in a background job.
[[ExceptionNotifier::Notifier, :background_exception_notification]].each do |object, method_name|
  raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true)
end

# Chain delayed job's handle_failed_job method to do exception notification
Delayed::Worker.class_eval do.
  def handle_failed_job_with_notification(job, error)
    handle_failed_job_without_notification(job, error)
    # only actually send mail in production
    if Rails.env.production?
      # rescue if ExceptionNotifier fails for some reason
      begin
        ExceptionNotifier::Notifier.background_exception_notification(error)
      rescue Exception => e
        Rails.logger.error "ExceptionNotifier failed: #{e.class.name}: #{e.message}"
        e.backtrace.each do |f|
          Rails.logger.error "  #{f}"
        end
        Rails.logger.flush
      end
    end
  end.
  alias_method_chain :handle_failed_job, :notification.
end

这样就可以出错时发送邮件提醒,另外也可以使用delayed_job自带的了handle_failed_exception方法

Time_with_zone

| Comments

time_with_zone

在rails中,server time跟传入的time参数在转化有时候会造成一些问题。

time

rails默认使用utc的时区存放time,但是在server中需要使用时区设置转换成本地时间

比如: utc为:’2012-08-08 12:00:00’, 而时区设置是:config.time_zone = ‘Beijing’, 即’+0800’ 那么当我们在console取出来事:’2012-08-08 20:00:00 +0800’

但是,有时候也会出现不一致的情况

time params with zone

1
params[:time] = '2012-08-08 12:00:00 +0700'

当上述params传入并save时,在console中取到的:

1
2
3
4
5
time = '2012-08-08 13:00:00 UTC'

#changed to local_time

time = '2012-08-08 21:00:00'

时间就多了一个小时,这是因为time中的’+0700’

当attribute是datetime时,rails会调用Time.zone.parse(original_time)与time.in_time_zone link

当没有时区参数的时候,time默认是utc的时区,所以当本地时区是+0800时, 它会减去8小时,将time从utc格式转换为+0800时区的格式。

而当有参数时(如+0700),time就是+0700时区的,会从+0700转换成+0800, 然后rails取出数据的话 默认是使用utc取出的,即会加上8小时,所以time就出现了错误。

如果要加上+0700的话,应该在application.rb中添加:

1
config.active_record.default_timezone = '#something respond to +0700'

然后存入跟取出的time就会一致了

后记

这个是time存入跟取出使用了两种不同时区而导致的错误

Proc Block

| Comments

Proc

在ruby中,block是一种隐形的方法参数, 而不是对象,在方法中直接调用不到, ,但是在方法中可以将其转化为proc对象,从而可以使用proc的方法。

&操作符

block转化为proc的方法是&操作符

block转化proc

1
2
3
4
5
6
7
def method(&block)
  puts block.class
end

method {}

=> Proc

此时的{} 是隐形的参数,method的表面参数是为0,如果传入的是proc对象, proc对象是表面的参数,则会报ArgumentError: wrong number of arguments (1 for 0)

proc转化为block

1
2
3
4
5
6
7
8
9
def method
  yield if block_given?
end

p = proc { puts 'proc' }

method &p

=> proc

此时的proc经过&转换,在method中变为block隐形的参数。 另:&p不可以单独使用, 不可在表达式中使用,如

1
2
3
4
5
&p
=> syntax error, unexpected tAMPER

1 == 1 ? &p : 1
=> syntax error, unexpected tAMPER

但是可以

1
2
3
self.send(:method, &p)

=> proc

结语

proc,lamdba,block还是很多不得不说的事….

Bundler Open

| Comments

bundler

bundler open 选项可以打开选定的local gem, 但是在使用的时候需要先指定$EDITOR或者$BUNDLER_EDITOR。

这时候可以先执行

1
export EDITOR=vim

然后执行

1
bundle open gem_name

这样就可以在vim中打开gem_name所在的目录了