Comments

《Ruby 元编程》学习笔记

作用域可以理解成变量在此位置上的可视区域。一般而言,可运行的代码是由代码体本身及一组 Binding 组成的,Binding 就是一个用对象表示的完整作用域,它们之间的关系可见下图:

Scope in Ruby

那么,怎么样才算是一个作用域呢?Ruby 会在以下三种情况关闭前一个作用域,同时打开一个新的作用域:

  • 类定义
  • 模块定义
  • 方法

这三种情况分别用 class, moduledef 作为关键词,这几个关键词也可以称为作用域门(Scope Gate)。请看以下示例代码:

v1 = 1

class MyClass  # 作用域门:进入class
  v2 = 2
  local_variables  #=> [:v2]

  def my_method  # 作用域门:进入 def
    v3 = 3
    local_variables  #=> [:v3]
  end  # 离开 def

  local_variables  #=> [:v2]
end  # 离开 class

obj = MyClass.new
obj.my_method #=> [:v3]
obj.my_method #=> [:v3]
local_variables #=> [:v1, :obj]

上述的代码中执行的两次 my_method,第一次执行时会打开一个。Ruby 不像 Java 那样有内部作用域(inner scope)和外部作用域(outer scope)的概念,它一旦进入一个新的作用域,原来的 Binding 就会被一组新的 Binding 替换。也就是说上述的代码总共创建了4个独立的作用域:顶级作用域、进入 MyClass 时创建的作用域和两次执行 my_method 时创建的作用域。

正如上面所说,我们在切换作用域的时候会把原来的 Binding 也一同替换,也就是说我们进入一个新的作用域时局部变量会即时失效,但往往我们想要在不同的作用域中共用局部变量,如下面的例子:

my_var = "Success"

class MyClass
  # 你想要在这里得到 my_var

  def my_method
    # 还有这里
  end
end

在这种情况下,我们想要让 Binding 跨越多个不同的作用域门从而共享局部变量,我们可以把作用域门的关键字替换为非作用域门的东西: Class.new 代替 classModule.new 代替 module,还有用 Module#define_method() 代替 def,这样我们就可以在闭包中取得局部变量的值,然后把这个闭包传递给替换后的方法。如下:

my_var = "Success"

MyClass = Class.new do
  puts "#{my_var} in the class definition!"

  define_method :my_method do
    puts"#{my_var} in the method!"
  end
end

MyClass.new.my_method

=> Success in the class definition!
   Success in the method!

这样的方法称做扁平化作用域(flattening the scope)。另外我们也可以在一个扁平作用域中定义多个方法,则这些方法可以用同一个作用域门进行保护,并共享绑定,这种技术叫做共享作用域。示例如下:

def define_methods
  shared = 0

  Kernel.send :define_method, :counter do
    shared
  end

  Kernel.send :define_method, :inc do |x|
    shared += x
  end
end

define_methods

counter  #=> 0
inc(4)
counter  #=> 4
Comments

通过 module 为类引入新的对象、方法会使我们的代码变得更加灵活,可扩展性更强,载入 module 一般使用 include 和 extend。

include & extend

当你定义好一个 module 后,你可以在你的类定义中指明 include 这个 module,让 module 的实例方法和变量成为类本身的实例方法和类变量。而 extend 这个 module 会把对应 module 的实例方法作为类方法加入到类中。

module Base 
 def class_type 
   “This class is of type: #{self.class}”
  end 
end 

class TestIncludeClass 
  include Base 
end 

class TestExtendClass
 extend Base
end

TestIncludeClass.new.class_type    #=>This class is of type: TestIncludeClass
TestExtendClass.class_type    #=>This class is of type: Class

P.S. include 只是引用了 module 的变量和实例方法,并不会把它们复制到类中,所有包含这个 module 的不同类都会指向同一个对象。

有时,我们希望能够在 module 里面同时定义类方法和实例方法,这时就可以通过included来实现。

module Base  
 def hello  
    puts "This is a instance method!"  
  end  

  # 扩展类方法  
  def self.included(base)  
    base.extend(ClassMethods)  
  end  

  # 类方法  
  module ClassMethods  
    def hello  
    puts "This is a class method!"  
    end  
  end   
end  

class TestClass  
  include Base  
end

TestClass.hello #=> "This is a class method!"
TestClass.new.hello #=> "This is a instance method!" 

在 Rails 中,我们可以很方便地使用 ActiveSupport::Concern 来简化这种实现,上述的 module 代码可写成:

require 'active_support/concern'

module Base
  extend ActiveSupport::Concern

  def hello  
    puts "This is a instance method!"  
  end

  module ClassMethods
    def hello  
      puts "This is a class method!"  
    end
  end
end

require & load

一般情况下,我们在开发的时候不一定会把 module 和 class 定义在同一个文件,为了方便管理,我们往往会把 module 定义在另一个文件,然后在类定义的文件中加载,这时就需要用到requireload

require方法让你加载一个库,并且只加载一次,如果你多次加载会返回false。只有当你要加载的库位于一个分离的文件中时才有必要使用require。使用时不需要加扩展名,一般放在文件的最前面:

require ‘test_library’

load用来多次加载一个库,你必须指定扩展名:

load ‘test_library.rb’

参考:

Comments

最近在复习算法,做下笔记。以前看到的一般都是用 C 语言实现,下面用 Ruby 作为伪语言分别实现冒泡排序和快速排序。

冒泡排序

算法原理:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码实现如下:

def bubble_sort(list) 
  list = list.dup
    for i in 0...list.length 
      for j in 0..(list.length - i - 2) 
        list[j], list[j + 1] = list[j + 1], list[j]  if list[j + 1] < list[j] 
      end 
    end 
    return list 
  end

快速排序

算法原理:

  1. 从数组中随机选出一个元素作为基数(pivot)。
  2. 将数组重新排列,比基数小的排在基数前面,比基数大的排在基数后面(跟基数相同的数可以放在任一边)。在这个分割结束后,这个基数就处于数列的中间位置,这个称为分割(partition)操作。
  3. 递归地把小于基数的元素子数列和大于基数的元素子数列排序。
  4. 当所有分割数组的大小是0或者1的时候,排序结束。

     def quicksort(list)
       return list if list.size <= 1
       pivot = list.sample
       left, right = list.partition { |e| e < pivot }
       quicksort(left) + quicksort(right)
     end
    

睡眠排序

这个算法的原理是针对数组里面的不同数开多个线程,每个线程根据数的大小睡眠,越大的数“睡”得越久,挺有意思。这个算法来源于这个帖子

    ARGV.each { |e| fork { sleep(e.to_f/1000); puts e } }

参考:

Comments

每个 Rails 团队的 Front End Engineer(F2E) 根据具体分工的不同因而或多或少地需要接触一些后台的知识,就我们团队而言主要需要了解的是:

  1. Views 的渲染机制(layouts, template, partial 三层之间的关系)
  2. Erb 的基本写法,简单的要能写,复杂点的(Form)至少要能看懂。
  3. Rails 的路由机制,可以通过config/routes.rbrake routes找到相应页面所在的文件位置。

上面3项相对比较难理解是路由部分,尤其是resources的概念对于初学者会比较难理解,针对这种情况,我简单设计了一个站点地图(doc/site-map.yml)

root:
  name: "Home page"
  url: "/"
  route_helper: root_url
  files:
    layout: "layouts/application" # By default
    header: "shared/headers/main"
    template: "sites/index"

contact:
  name: "Contact Us"
  url: "/contacts/new"
  route_helper: new_contact_url
  files:
    header: "shared/headers/main"
    template: "contacts/new"

sport_facility:
  overview:
    name: "Facility Overview"
    url: "/sport-facilities"
    route_helper: sport_facilities_url
    files:
      header: "shared/headers/sport_facility"
      template: "sport_facilities/overview"
  basketball_courts:
    name: "Basketball Courts"
    url: "/sport-facilities/basketball-courts"
    route_helper: basketball_courts_url
    files:
      header: "shared/headers/sport_facility"
      template: "sport_facilities/basketball_courts"
  ball_hockey:
    name: "Ball Hockey"
    url: "/sport-facilities/ball-hockey"
    route_helper: ball_hockey_url
    files:
      header: "shared/headers/sport_facility"
      template: "sport_facilities/ball_hockey"

主要有以下几个作用:

  • 在项目初期就先去写这份文档,在一定程度上可以增加我们对项目的整体认识,同时也可以在这个过程设计出对 SEO 友好的 URL(在项目中后期再设计的话会花更多的时间)。
  • 把项目所有页面都结构化地整理了出来,刚接触这个项目的成员可以通过这个站点地图很快能对这个项目的结构有宏观的了解。
  • 建立了页面名字、页面 URL、生成该 URL 的 Helper 方法还有这个页面的相关文件之间的联系,这样 F2E 就不需要去理解config/routes.rb的代码,可以直接通过这份文档去找到他们需要做的页面位置,他们在写链接的时候也可以直接用上面的route_helper,我们修改了页面的结构之类的了只需要维护这份文档就可以省去他们的很多疑问。

也许通过团队之间必要的沟通和互动可以很好地简化上述的过程,并不需要维护这样的一份文档,不过以我们团队的情况而言,我们是以远程协助开发为主,由于时差的问题,团队成员并不能全部在同一时间内工作,因而必要的规范可以节省高额的沟通成本。这个只是我们为了提高团队开发效率所做的一种摸索,欢迎交流。

Comments

好吧,我承认标题有点小邪恶,还是直入正题吧:-)

Pow

Pow37signals出品的一款简洁的 Rack 服务器,尤其在多应用同时开发时会更便捷。Pow 的安装很简单:

curl get.pow.cx | sh
cd ~/.pow
ln -s /path/to/myapp

Powder

现在你就可以通过http://myapp.dev打开我们的应用了。 同时,使用 powder 来管理 Pow 会更简洁。安装如下:

gem install powder

安装后我们就可以在项目目录里面执行powder link,powder 会帮我们自动在 ~/.pow/<current_directory> 的目录下生成快捷方式,接下来执行powder open就可以直接打开应用了。你可以在项目目录执行powder查看它的其他命令。

Pry

在开发的过程中我们可以执行powder applog查看项目日志,另外我比较喜欢用 Pry 进行debug,在 Pry 的 wiki 上有说明如何跟 Pow 配合使用。

简单点说,就是先安装 Pry-remote 这个 gem,然后在需要加断点的地方加上binding.remote_pry,等页面停留在断点的位置时就在项目的目录执行pry-remote,其他的就跟 Pry 完全一样了。

说到 Pry 顺带提一下 jazz_hands 这个 gem,装了后在 pry 的终端里面显示的效果好很多 :-)

Copyright © 2013 - Zernel Guan - Powered by Octopress