Jonny Zheng

I'm an idealist, not perfectionist

IOS PrettyKit 简介

最近发现一个很不错的ios lib PrettyKit, PrettyKit库提供了一套可以自定义Grouped TableCell, NavigationBar, Tab Bar 的子类,它为我们提供了更多的可定义属性,比如边框,阴影,圆角,渐变等等,大大方便了开发,节省了我不少的时间。

用户设置是每个app都用到的功能,一般我们都会用group table来做,不过要想做的漂亮还是要花一番心思,下面我们通过使用PrettyKity来示范一下做个设置界面

新建项目,引用文件

首先新建一个项目,clone PrettyKit的代码,把PrettyKit文件夹拷贝到新项目的Vendor目录下,然后在项目中添加进来。

这里要注意一点,PrettyKit的代码不要用原来的那个,原来的作者已经很久没有维护过项目了,如果你是使用ARC的,请使用我从其他分支整合过来的版本,这个版本是支持ARC的,而且修复了一些小Bug: https://github.com/jonnyzheng/PrettyKit

PrettyTableViewCell 的使用

如果是做类似设置界面这样的静态Table,我们可以通过在StoryBoard的界面里设置选择 Cell的 Class是PrettyTableViewCell就直接设定好了Table Cell的类,如果没有用StoryBoard或者是需要动态的生成的Cell, 那么我们就需要自己写代码了,下面介绍一下如何自己写:

首先给Table View换个好看点的颜色

1
2
3
4
5
6
7
8
9
- (void)viewDidLoad
{
    [super viewDidLoad];

    [self.tableView setBackgroundView:nil];
    self.tableView.backgroundColor = [UIColor colorWithRed:248.0/255.0
                                                     green:244.0/255.0
                                                      blue:239.0/255.0 alpha:1.0];
}

自己定义一下有多少个Group,每个Group有多少行

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
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 4;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    if (section == 0) {
        return 1;
    }
    if (section == 1) {
        return 2;
    }
    if (section == 2) {
        return 4;
    }

    if (section == 3) {
        return 1;
    }


    return 0;
}

为每行生成Cell

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
32
33
34
35
36
37
38
39
40
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    PrettyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[PrettyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                          reuseIdentifier:CellIdentifier];

        switch (indexPath.section) {
            case 0:
                switch (indexPath.row) {
                    case 0:
                        [self prepareNameCell:cell];
                        break;
                }
                break;
            default:
                break;
        }

        //设置圆角弧度和阴影
        cell.cornerRadius = 5;
        cell.shadowOpacity = 0.07;
        cell.backgroundColor = [UIColor whiteColor];

        //设置选择时候的颜色
        cell.selectionGradientStartColor = [UIColor colorWithRed:248.0/255.0
                                                           green:244.0/255.0
                                                            blue:239.0/255.0 alpha:1];
        cell.SelectionGradientEndColor = [UIColor colorWithRed:248.0/255.0
                                                         green:244.0/255.0
                                                          blue:239.0/255.0 alpha:1];
        // Configure the cell...
        [cell prepareForTableView:tableView indexPath:indexPath];

    }
    return cell;

}

自定义Cell

上面的代码里除了第一行其他都是生成的空Cell, 现在来看看如何实现第一行的prepareNameCell方法

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
-(void) prepareNameCell:(PrettyTableViewCell *)cell
{
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

    UILabel *nameLable = [[UILabel alloc] initWithFrame:CGRectMake(5.0, 5.0, 100.0, 15.0)];
    nameLable.text = @"用户名:";
    nameLable.font = [UIFont fontWithName:@"Helvetica-Bold" size:15.0];
    nameLable.translatesAutoresizingMaskIntoConstraints = NO;
    [cell.contentView addSubview:nameLable];


    UILabel *name = [[UILabel alloc] initWithFrame:CGRectMake(5.0, 0.0, 100.0, 15.0)];
    name.text = @"jonny";
    name.translatesAutoresizingMaskIntoConstraints = NO;
    name.font = [UIFont fontWithName:@"Helvetica" size:15.0];

    [cell.contentView addSubview:name];


    NSLayoutConstraint *constraintXName = [NSLayoutConstraint constraintWithItem:name
                                                                        attribute:NSLayoutAttributeLeading
                                                                        relatedBy:NSLayoutRelationEqual
                                                                           toItem: nameLable
                                                                        attribute:NSLayoutAttributeTrailing
                                                                       multiplier:1.0 constant:5.0];


    NSLayoutConstraint *constraintYName = [NSLayoutConstraint constraintWithItem:name
                                                                        attribute:NSLayoutAttributeCenterY
                                                                        relatedBy:NSLayoutRelationEqual
                                                                           toItem: cell.contentView
                                                                        attribute:NSLayoutAttributeCenterY
                                                                       multiplier:1.0 constant:0];

    NSLayoutConstraint *constraintX = [NSLayoutConstraint constraintWithItem:nameLable
                                                                   attribute:NSLayoutAttributeLeading
                                                                   relatedBy:NSLayoutRelationEqual
                                                                      toItem: cell.contentView
                                                                   attribute:NSLayoutAttributeLeading
                                                                  multiplier:1.0 constant:10.0f];


    NSLayoutConstraint *constraintY = [NSLayoutConstraint constraintWithItem:nameLable
                                                                   attribute:NSLayoutAttributeCenterY
                                                                   relatedBy:NSLayoutRelationEqual
                                                                      toItem: cell.contentView
                                                                   attribute:NSLayoutAttributeCenterY
                                                                  multiplier:1.0 constant:0];



    [cell.contentView addConstraints: [[NSArray alloc] initWithObjects:
                                       constraintXName,constraintYName,constraintX,constraintY,nil]];
}

上面的方法中用到了不少NSLayoutConstraint的布局方式,如果不需要也可以用绝对定位。

Ok, 一个简单的设置页就做好了,完整代码请看这里https://gist.github.com/jonnyzheng/6261968 。 当然PrettyKit里还有一个比较全的样例项目,大家也可以参考,玩的开心~~~

Elixir语言特性简介

Erlang真是一门让人又爱又恨的语言,爱的是他天生的分布式能力,恨的是有点变态的语法,可以把人搞晕的标点符号,稍不注意就一堆大小括号的方法调用. 不过最近有一个开源项目让我小兴奋了一下,名字叫elixir. elixir是一个基于Erlang VM的编程语言,它在语法上大量借鉴了ruby的元编程思想,同时有一些自己的很有亮点的地方,而本身又与Erlang完全兼容,很好的将Erlang的语法缺点去除,保留了分布式的特性,是一门很有想象空间的语言。

接下来通过比较语法点的方式来吐槽一下Erlang语法,展示一下Elixir的优秀之处。

atom和变量

Erlang

Erlang中的Atom是以小写字母开始的一个单词,变量是首字母大写的单词,因为只是首字母大小写的关系很容易让人区分不清,尤其是在Tuples这样的数据结构中和变量的区分就更不明显了:

1
{a,B,C} =  Person

人的大脑是很有趣的,他们能够模糊的处理一些事物,通常情况下可以忽略字母的大小写认为上面的a B C性质上是一样的东西,可谁知道Erlang的定义中小写和大写有这么明显的不同,a是atom,而B C是变量。

Elixir

Elixir用冒号加单词的方式来表示atom,变量也不需要首字母大写,都是小写的。

1
{:a,b,c} = person

这里的:a就是一个atom,是不是让你想到了ruby里的Symbol,而b,c是变量。这样的表示要清晰明确的多,妈妈再也不用担心我给搞浑了。

混乱的结束标识

Erlang

在Erlang中一行的结束用.表示,刚知道这个语法的时候感觉非常不适应,当碰到浮点数的写起来也很怪异

1
Pi = 3.14159.

如果只有.做为结束标识我也就懒得吐槽了,问题是根据不同的控制结构还有各种各样的方式标识结束,其中包括了 , ; end 以及无符号,虽说熟了之后也知道都是代表什么意思,但写代码的时候却总是不放心想去查查看结束符有没有用对,嗨~~~~

1
2
3
4
odds_and_evens(L) ->
    Odds = [X || X <- L, (X rem 2) =:= 1],
    Evens = [X || X <- L, (X rem 2) =:= 0],
    {Odds, Evens}.

上面这个比较简单在一个方法体内,最后一行用.表示结束,之前的行需要用,, 接下来再看个难的

1
2
3
4
5
6
7
filter(P, [H|T]) ->
	case P(H) of
		true -> [H|filter(P, T)];
		false -> filter(P, T)
	end;
filter(P, []) ->
	[].

是不是看着有点糊涂了,怎么有的结束是;, 有的没有符号,有的是 end;, 如果没有学过Erlang的同学可以去看看 case语法以及方法的匹配模式。总之在一个结束标识上搞出这么多花样,真是苦了我们这些码农。

Elixir

Elixir处理结尾很简单,就是没有符号,和ruby一样,所有方法都用end结束, Less is More。

1
2
3
def hello
	IO.puts "hello world"
end

比较运算符

Erlang
1
2
3
4
5
6
7
8
X > Y X is greater than Y.
X < Y X is less than Y.
X =< Y X is equal to or less than Y.
X >= Y X is greater than or equal to Y.
X == Y X is equal to Y.
X /= Y X is not equal to Y.
X =:= Y X is identical to Y.
X =/= Y X is not identical to Y

前面几个还挺正常的,后三个就又让人有些不淡定了,不等于竟然这么写/=, 严格的等于是 =:=, 严格的不等于是=/=, 这种新颖的表示方式让我们这些用过不少其他语言的同学感到很不适应。

Elixir

Elixir的处理只能用很正常来表示,呵呵:

1
2
3
4
5
6
7
8
a === b # strict equality (so 1 === 1.0 is false)
a !== b # strict inequality (so 1 !== 1.0 is true)
a == b # value equality (so 1 == 1.0 is true)
a != b # value inequality (so 1 != 1.0 is false)
a > b # normal comparison
a >= b # :
a < b # :
a <= b # :

这个点其实也没有什么太大的问题,Erlang只是没有按照大部分语言的习惯做法来罢了,如果有人觉的这样没什么不习惯那也能够理解,我其实仅仅是想知道Erlang的这种表示方式的来源或者目的是什么。

发送消息

Erlang

Erlang中进程间的通信是依靠互相发送消息进行的:

1
Pid ! Message

语法很简单,Pid是进程,Message是消息,中间用 ! 分隔

Elixir

Elixir对消息发送的语法只做了简单的改变,但看上去要相当人性化

1
Process <- Message

很显然有了<-在中间表达了一种指向性的涵义,根据这样的语法来理解消息的传递要容易的多。

嵌套方法的调用

Erlang

当在调用方法时经常会用嵌套的方法减少变量的定义,但是用多了会很难看,当然这不光是Erlang语言有这个问题,其他的语言也一样有。

1
list_to_atom(binary_to_list(capitalize_binary(list_to_binary(atom_to_list(X))))).
Elixir

Elixir为了简化大量的中间变量,使用了一个叫做Pipe Operator的语法。看看上面那句在Elixir中我们可以这样写:

1
X |> atom_to_list |> list_to_binary |> capitalize_binary |> binary_to_list |> binary_to_atom

这就相当于Unix里的管道,把前一个命令的结果当做参数,传递给下个命令。在Elixir里面结果做为下一个方法的第一参数传入,产生结果后在传给下一个,直到最后。管道的使用大大优化了程序的可读性,我认为算是Elixir里面最有亮点的一个特性啦。

字符串拼接

Erlang

Erlang的字符串拼接是很麻烦的事情,尤其是想在一句话中间拼接变量的时候

1
2
Name = "jonny".
Sentence = "This is " ++ Name ++ "speaking".
Elixir

这里借鉴了Ruby的表达方式,可以在双引号的字符串中连接变量

1
2
name = "jonny"
sentence = "This is #{name} speaking"

期望

现在想想Ruby为什么会成功,就是因为他优雅的设计吸引了很多优秀的Programmer, 让更多的人真的感受到了编码的乐趣,大家都接受Ruby的设计哲学,用同一种思维方式来把不同的问题抽象成为代码,形成了现在这样一个积极向上的优秀社区。那么我想在Elixir身上我也看到了这种潜力,虽然是一门新生的语言,他继承了Erlang的所有优点,语法上融入了Ruby的人性化编程的精髓,也许在不久的将来会成为并发编程中非常热门的语言.

学习资料

Erlang

Erlang官网 http://www.erlang.org/

Programming Erlang http://pragprog.com/book/jaerlang/programming-erlang
O’REILLY Erlang http://shop.oreilly.com/product/9780596518189.do
Learn you some erlang(一本在线电子书) http://learnyousomeerlang.com/

Elixir

Elixir官网 http://elixir-lang.org/
Programming Elixir http://pragprog.com/titles/elixir

MarsGeo

MarsGeo是一个将真实经纬度转换成火星地址的Gem. 我们在做手机App开发时经常会用到地图,通常手机获得的经纬度放到 Google地图的时候会有几百米的偏差,这是因为某些原因国内地图都做了偏移加密,这个俗称火星坐标系。貌似火星坐标是没办法转换成真实的坐标滴,所以一种变通的做法就是把真实经纬度转换成火星坐标系好和Google地图匹配。

MarsGeo是根据网上的一段Java代码翻译过来的,所以具体的算法并不是很了解,,至于准确性如何 我没有全面测试过,不过我自己使用的情况还是挺好的,欢迎大家测试反馈。

具体使用见github

顺便说下,如果用的是百度地图的同学是不需要这个Gem的,百度有专门的转换API, 这点百度还是做的挺方便的。

Rails源码学习(active_support:lazy_load_hooks)

在rails的initialize代码中经常能看到方法 ActiveSupport.on_load,这个代码是起什么用的引起了我很大的好奇,于是看了看源码,觉得这个功能还是挺好玩而且有用的。

ActiveSupport.on_load方法为rails提供了一种叫做 lazy_load_hooks的功能,这个功能可以使很多组件在被用到时才被加载,而不是启动即全部加载,这样大大加快了rails app 的启动速度。

lazy_load_hooks的代码很短,只有不到30行,文件名: activesupport-3.2.x/lib/active_suport/lazy_load_hooks.rb

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
module ActiveSupport
  @load_hooks = Hash.new { |h,k| h[k] = [] }
  @loaded = Hash.new { |h,k| h[k] = [] }

  def self.on_load(name, options = {}, &block)
    @loaded[name].each do |base|
      execute_hook(base, options, block)
    end
    @load_hooks[name] << [block, options]
  end

  def self.execute_hook(base, options, block)
    if options[:yield]
      block.call(base)
    else
      base.instance_eval(&block)
    end
  end

  def self.run_load_hooks(name, base = Object)
    @loaded[name] << base
    @load_hooks[name].each do |hook, options|
      execute_hook(base, options, hook)
    end
  end
end

代码中会初始化两个Hash变量 @load_hooks@loaded, @load_hooks用来存放还没有被执行的blocks, @loaded用来存放已经被加载的载体类。

让我们看看一个ActiveRecord的例子来了解一下lazy_load_hooks的使用方法,在activerecord-3.2.x/lib/active_record/railtie.rb文件中有很多的initializer块,这些内容在rails启动的时候都会被调用到,像下面这个加载logger的:

1
2
3
initializer "active_record.logger" do
    ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
end

这时候 ActiveSupport.run_load_hooks还没有执行,所以 on_load方法中@loaded[:active_record]是空的,里面的each循环不会执行,执行到的是

1
@load_hooks[name] << [block, options]

把block放进@load_hooks[:active_record]里面去,所以在railtie里的所有on_load的block都先被存放了起来。

最后的执行是在activerecord-3.2.x/lib/active_record/base.rb文件中,代码的最下面执行到了run_load_hooks方法,

1
ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)

当加载ActiveRecord::Base执行到该方法,并且把ActiveRecord::Base作为载体类传了进去。所以在ActiveRecord::Base被加载的时候所有的lazy_load block才会被执行到,同样的其他模块中的railite里也有很多lazy_load block,他们都是在需要的时候才被执行,这样又节省运行资源,又节省启动时间,我们自己写Gem的时候也可以用上啦。

Rails 源码学习3 (Application,Engine,Railtie)

在第一篇Rails 的初始化过程里讲到rails的启动命令是在 railties/lib/rails/commands/server.rb 里执行的,其实这里的server类是继承自Rack::Server的,从这里就可以看出rails是一个rack app,rack 连接了Rails应用和 WebServer, 现在让我们再深入进去看看rails的各个组件是如何被整合在一起的。

在 Rails::Server类里一些方法被覆写,用于加载rails自己的config环境,middleware等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 覆盖了rack的middleware 方法,加载了3个middleware app
def middleware
      middlewares = []
      middlewares << [Rails::Rack::LogTailer, log_path] unless options[:daemonize]
      middlewares << [Rails::Rack::Debugger]  if options[:debugger]
      middlewares << [::Rack::ContentLength]
      Hash.new(middlewares)
end




# 默认端口改成3000,定义了rack的启动文件config.ru, 还有一些默认配置
def default_options
      super.merge({
        :Port        => 3000,
        :environment => (ENV['RAILS_ENV'] || "development").dup,
        :daemonize   => false,
        :debugger    => false,
        :pid         => File.expand_path("tmp/pids/server.pid"),
        :config      => File.expand_path("config.ru")
      })
end

rails项目里的 config.ru

rack最后会执行config.ru,然后看看rails项目的根目录下config.ru里是如何写的

1
2
3
4
5
#引用了config下面的 enviroment.rb 
require ::File.expand_path('../config/environment',  __FILE__)

# RailsStudy是我的rails的项目名称,这里的工作就是run 这个Application
run RailsStudy::Application

我们的Rails代码就是在这一步开始被调用到的

下面来看看 config/application.rb 这个文件

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
require File.expand_path('../boot', __FILE__)

require 'rails/all'

if defined?(Bundler)
  # If you precompile assets before deploying to production, use this line
  Bundler.require(*Rails.groups(:assets => %w(development test)))
  # If you want your assets lazily compiled in production, use this line
  # Bundler.require(:default, :assets, Rails.env)
end

module RailsStudy
  class Application < Rails::Application
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.

    # Custom directories with classes and modules you want to be autoloadable.
    # config.autoload_paths += %W(#{config.root}/extras)

    # Only load the plugins named here, in the order given (default is alphabetical).
    # :all can be used as a placeholder for all plugins not explicitly named.
    # config.plugins = [ :exception_notification, :ssl_requirement, :all ]

    # Activate observers that should always be running.
    # config.active_record.observers = :cacher, :garbage_collector, :forum_observer

    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
    # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
    # config.time_zone = 'Central Time (US & Canada)'

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
    config.i18n.default_locale = "zh-CN"

    # Configure the default encoding used in templates for Ruby 1.9.
    config.encoding = "utf-8"

    # Configure sensitive parameters which will be filtered from the log file.
    config.filter_parameters += [:password]

    # Enable escaping HTML in JSON.
    config.active_support.escape_html_entities_in_json = true

    # Use SQL instead of Active Record's schema dumper when creating the database.
    # This is necessary if your schema can't be completely dumped by the schema dumper,
    # like if you have constraints or database-specific column types
    # config.active_record.schema_format = :sql

    # Enforce whitelist mode for mass assignment.
    # This will create an empty whitelist of attributes available for mass-assignment for all models
    # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
    # parameters by using an attr_accessible or attr_protected declaration.
    config.active_record.whitelist_attributes = true

    # Enable the asset pipeline
    config.assets.enabled = true

    # Version of your assets, change this if you want to expire all your assets
    config.assets.version = '1.0'
  end
end

这是一个默认的application文件,基本没有修改里面的内容. 在rails项目里application文件的作用主要是做一些初始化的设置,比如时区,i18n等等。

RailsStudy::Application 类是继承自 Rails::Application,然后我们去看看这个文件在哪里

railties/lib/rails/application.rb

application文件里做了详细的介绍 Application的功能:

  • Initialization:
    Rails::Application负责执行所有的railties,engines,plugin的 initializer, 并且也执行 bootstrap initializer 和 finishing initializer

  • Configureation:
    负责设置App的config,其中包含了 Rails::Engine和Rails::Railtie的config

  • Routes:
    负责管理routes,当文件改变的时候reload所有的routes

  • Middlewares:
    负责构建middlewares的堆栈

  • Booting process:
    负责按照启动顺序启动和执行程序,大致程序如下

    1. require “config/boot.rb” to setup load paths
    2. require railties and engines
    3. Define Rails.application as “class MyApp::Application < Rails::Application”
    4. Run config.before_configuration callbacks
    5. Load config/environments/ENV.rb
    6. Run config.before_initialize callbacks
    7. Run Railtie#initializer defined by railties, engines and application.
    8. One by one, each engine sets up its load paths, routes and runs its config/initializers/* files.
    9. Custom Railtie#initializers added by railties, engines and applications are executed
    10. Build the middleware stack and run to_prepare callbacks
    11. Run config.before_eager_load and eager_load if cache classes is true
    12. Run config.after_initialize callbacks

这里先介绍一下application的功能,到最后我们再去看代码,接下来先看看Railtie。

railties/lib/rails/railtie.rb

Rails::Application < Rails::Engine < Rails::Railtie

Railties 是rails framework的核心,用来提供各种回调方法以及扩展,或者修改初始化的process. 实际上每个rails组件(ActionMailer,Action Controller)都是一个railtie, 每个组件都有自己的初始化代码,这样rails就不用去专门把初始化工作都自己来做,只要在正确的时候调用各组件的初始化代码就可以了。

Railtie的代码其实很短,只有100多行,类中一开始就引用了下面几个模块:

1
2
3
4
autoload :Configurable,  "rails/railtie/configurable"
autoload :Configuration, "rails/railtie/configuration"

include Initializable

Configurable & Configuration

rails/railtie/configurable 中的类方法moudule中定义了 instance方法,并且将 @instance的config 方法代理给了include自己的类,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module Rails
  class Railtie
    module Configurable
      extend ActiveSupport::Concern

      module ClassMethods

        delegate :config, :to => :instance

        def inherited(base)
          raise "You cannot inherit from a #{self.superclass.name} child"
        end

        def instance
          @instance ||= new
        end

        ...
      end
    end
  end
end

这里的@instance有可能是Railtie,Engine,Application类的实例,他们都有自己的config方法实现,方法里基本上都是生成了自己的Configuration实现,这些Configuration类也是依次的继承关系: Application::Configuration < Engine::Configuration < Railtie::Configuration

Initializable

railties/lib/rails/rails/initializable.rb 中定义了Module Rails::Initializable, 所有include了该Module的类都拥有了 initializer,initializers,initializers_chain 等类方法,initializer 方法帮助类可以定义一个或多个自己的 initializer, 并且通过initializers,initializers_chain来获取他们。

比如用来加载i18n的本地化文件路径:

1
2
3
initializer :add_locales do
  config.i18n.railties_load_path.concat(paths["config/locales"].existent)
end
1
2
3
4
5
def initializer(name, opts = {}, &blk)
        raise ArgumentError, "A block must be passed when defining an initializer" unless blk
        opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] }
        initializers << Initializer.new(name, nil, opts, &blk)
end

在方法中会生成一个Initializer的实例,然后加入到@initializers变量中,最后会在Application类中的initialize!方法中会被执行。

railties/lib/rails/engine.rb

Rails::Engine用来生成一个简单的Rails应用的子集,打包成通用的程序供不同的Rails项目使用。 从Rails 3.0开始Rails应用就是一个Engine, 实际上Application类是继承自Engine类的。

我们写的Gem包也可以是一个Engine应用,里面可以包含自己的controller,model,view文件,文件结构也可以和rails的文件结构一样,这样的构架很方便做一些通用的功能包,为减少重复代码提供了极大的方便。Engine本身还是有很多内容可以说的,关于如何应用他可以看看这个教程:Getting Started with Engines

再回过头来看代码,首先 Engine类是继承于 Railtie 的,也就是拥有了Railtie类的基本功能,但同时Engine类也扩展了自己的configuration类,这里主要为一个Engine定义了他的文件结构:

railties/lib/rails/engine/configuration.rb

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
  def paths
    @paths ||= begin
      paths = Rails::Paths::Root.new(@root)
      paths.add "app",                 :eager_load => true, :glob => "*"
      paths.add "app/assets",          :glob => "*"
      paths.add "app/controllers",     :eager_load => true
      paths.add "app/helpers",         :eager_load => true
      paths.add "app/models",          :eager_load => true
      paths.add "app/mailers",         :eager_load => true
      paths.add "app/views"
      paths.add "lib",                 :load_path => true
      paths.add "lib/assets",          :glob => "*"
      paths.add "lib/tasks",           :glob => "**/*.rake"
      paths.add "config"
      paths.add "config/environments", :glob => "#{Rails.env}.rb"
      paths.add "config/initializers", :glob => "**/*.rb"
      paths.add "config/locales",      :glob => "*.{rb,yml}"
      paths.add "config/routes",       :with => "config/routes.rb"
      paths.add "db"
      paths.add "db/migrate"
      paths.add "db/seeds",            :with => "db/seeds.rb"
      paths.add "vendor",              :load_path => true
      paths.add "vendor/assets",       :glob => "*"
      paths.add "vendor/plugins"
      paths
    end
  end

railties/lib/rails/engine/railties.rb

Engine的Railties类是用来获取相关的railties,如self.railies方法可以获得当前环境下所有 继承了::Rails::Railtie 的类的实例, self.engines则可以得到所有继承::Rails::Engine 的实例。

回到engine.rb, 这里定义了很多initializer,大部分的initializer是用来查看并加载所需要的文件用的。

1
2
3
4
set_autoload_paths #设置需要自动load的文件路径
add_routing_paths #如果routes文件存在则load相关文件
add_locales  #如果本地化文件存在则load相关文件
load_config_initializers #如果有initializer文件存在则load相关文件

railties/lib/rails/application.rb

了解完Engine后再来看看Application的代码, 首先是

1
2
3
4
5
6
7
8
9
10
class Application < Engine
    autoload :Bootstrap,      'rails/application/bootstrap'
    autoload :Configuration,  'rails/application/configuration'
    autoload :Finisher,       'rails/application/finisher'
    autoload :Railties,       'rails/application/railties'
    autoload :RoutesReloader, 'rails/application/routes_reloader'


	...
end

rails/application/bootstrap 定义启动是最开始需要加载的initializers,比如加载Logger,加载ActiveSupport的所有功能。

rails/application/configuration 是继承于 ::Rails::Engine::Configuration,这里他提供了一个Rails Application所需要的更多config options:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module Rails
  class Application
    class Configuration < ::Rails::Engine::Configuration
      attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets,
                    :cache_classes, :cache_store, :consider_all_requests_local,
                    :dependency_loading, :exceptions_app, :file_watcher, :filter_parameters,
                    :force_ssl, :helpers_paths, :logger, :log_tags, :preload_frameworks,
                    :railties_order, :relative_url_root, :reload_plugins, :secret_token,
                    :serve_static_assets, :ssl_options, :static_cache_control, :session_options,
                    :time_zone, :reload_classes_only_on_change, :whiny_nils


   ...
end

更多的paths:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 def paths
        @paths ||= begin
          paths = super
          paths.add "config/database",    :with => "config/database.yml"
          paths.add "config/environment", :with => "config/environment.rb"
          paths.add "lib/templates"
          paths.add "log",                :with => "log/#{Rails.env}.log"
          paths.add "public"
          paths.add "public/javascripts"
          paths.add "public/stylesheets"
          paths.add "tmp"
          paths
        end
      end

rails/application/finisher.rb 用来定义在启动完成前需要加载的initializers rails/application/railties.rb 用来获得所有的railties,包括engines,plugins,railties。 rails/application/routes_reloader.rb 用来提供检测文件改动和reload routes的功能。

initialize!方法在每个Rails项目的config/enviroment.rb中被调用到,这时会真正的执行所有定义好 的initializers

1
2
3
4
5
6
def initialize!(group=:default) #:nodoc:
    raise "Application has been already initialized." if @initialized
    run_initializers(group, self)
    @initialized = true
    self
end

每一个initializer最后在 Rails::Initializable::Initializer类的run方法中被执行到:

1
2
3
def run(*args)
    @context.instance_exec(*args, &block)
end

default_middleware_stack方法为rails程序加载默认的middleware, 该方法在Engine类的app方法中被调用到。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def default_middleware_stack
      ActionDispatch::MiddlewareStack.new.tap do |middleware|
        if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache
          require "action_dispatch/http/rack_cache"
          middleware.use ::Rack::Cache, rack_cache
        end

        if config.force_ssl
          require "rack/ssl"
          middleware.use ::Rack::SSL, config.ssl_options
        end

        if config.serve_static_assets
          middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control
        end

        middleware.use ::Rack::Lock unless config.allow_concurrency
        middleware.use ::Rack::Runtime
        middleware.use ::Rack::MethodOverride
        middleware.use ::ActionDispatch::RequestId
        middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods
        middleware.use ::ActionDispatch::ShowExceptions, config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path)
        middleware.use ::ActionDispatch::DebugExceptions
        middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies

        if config.action_dispatch.x_sendfile_header.present?
          middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
        end

        unless config.cache_classes
          app = self
          middleware.use ::ActionDispatch::Reloader, lambda { app.reload_dependencies? }
        end

        middleware.use ::ActionDispatch::Callbacks
        middleware.use ::ActionDispatch::Cookies

        if config.session_store
          if config.force_ssl && !config.session_options.key?(:secure)
            config.session_options[:secure] = true
          end
          middleware.use config.session_store, config.session_options
          middleware.use ::ActionDispatch::Flash
        end

        middleware.use ::ActionDispatch::ParamsParser
        middleware.use ::ActionDispatch::Head
        middleware.use ::Rack::ConditionalGet
        middleware.use ::Rack::ETag, "no-cache"

        if config.action_dispatch.best_standards_support
          middleware.use ::ActionDispatch::BestStandardsSupport, config.action_dispatch.best_standards_support
        end
      end
    end

嗯,基本上就是这回所有的内容了,总结一下: Railtie为所有rails组件提供了和Rails整合的接口,使他们可以定义自己的初始化内容,可以有自己的配置。 Engine继承于Railtie, 提供了一个功能最小化的Rails文档结构,Application继承于Engine, 在Engine的基础上定义了一个完整Rails所需要的Initializers和middleware,同时在启动时负责加载Rails程序中其他组件的所有Initializers。

Rails 源码学习 2 (Rack and Webrick)

先来一个rack程序的简单例子

1
2
3
4
5
6
7
8
9
10
11
require 'rubygems'
require 'rack'


class HelloWorld
  def call(env)
    return [200, {}, ["Hello world!"]]
  end
end

Rack::Handler::WEBrick.run(HelloWorld.new,:Port => 9000)

HelloWorld 方法里定义了一个call方法,call方法只有一个参数env, 方法会return一个数组,里面包含了http请求的response. 运行一下这个文件看看:

1
2
3
4
$ ruby myserver.rb
[2012-08-24 12:24:28] INFO  WEBrick 1.3.1
[2012-08-24 12:24:28] INFO  ruby 1.9.3 (2012-04-20) [x86_64-darwin11.4.0]
[2012-08-24 12:24:28] INFO  WEBrick::HTTPServer#start: pid=17920 port=9000

我们通过 WebBrick的run方法启动WebBrick Server, 并且传递了HelloWorld的实例, 现在端口在9000, 打开网页访问 http://localhost:9000 就会看到页面里显示的是 Hello world!.

rackup

rack 本身就提供了一个 rackup 工具, 我们可以用rackup工具启动一个文件,不过这个文件要是 .ru后缀结尾的,默认名称是config.ru, 这个文件是不是很眼熟, 其实在每个rails项目的根目录下都有这个文件。让我们来写一个config.ru文件

1
run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Hello Rack!"]]}

好了,在文件目录里运行一下

1
2
3
4
$ rackup config.ru
[2012-08-24 14:56:34] INFO  WEBrick 1.3.1
[2012-08-24 14:56:34] INFO  ruby 1.9.3 (2012-04-20) [x86_64-darwin11.4.0]
[2012-08-24 14:56:34] INFO  WEBrick::HTTPServer#start: pid=18286 port=9292

这回是绑定在9292端口,rack的默认端口上。

middleware

rack 的middleware 提供对任何请求的filter功能,比如我们可以为每次请求记录log, 给每个html页面里加入一些信息,有了middleware接口, 实现起来就非常方便了,而且我们可以堆叠所有的middleware,每一个middleware处理完后传给下一个,直到没有middleware了才会把结果输出出来, 把上面的例子再改一改,为每次输出里面都增加一个字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class InsertName
    def initialize(app)
      @app = app
    end

    def call(env)
      status, headers, response = @app.call(env)
      [status,headers, [response[0]+ ' jonny']]
    end
end

use InsertName

run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Hello Rack!"]]}

重新用rackup启动,访问 localhost:9292, 会看到结果是 Hello Rack! jonny

middleware的应用非常灵活强大,rails的很多功能都是基于rack middleware实现的,在rails项目下输入 rack middleware 会列出项目用到的所有middleware, 下面是我的一个项目的middleware列表:

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
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007facbb14adb0>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use ActionDispatch::Head
use Rack::ConditionalGet
use Rack::ETag
use ActionDispatch::BestStandardsSupport
use Warden::Manager
use OmniAuth::Strategies::Weibo
run Opinion::Application.routes

rack handler

rack handler 是rack链接server的桥梁,rack 库自带了很多server端的handler, 我们用webrick的handler做示例,在第一个例子里我们就是用的handler启动的Webrick server: Rack::Handler::WEBrick.run(HelloWorld.new,:Port => 9000)

  • rack/lib/rack/handler/webrick.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module Rack
  module Handler
    class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
	  def self.run(app, options={})
        options[:BindAddress] = options.delete(:Host) if options[:Host]
        @server = ::WEBrick::HTTPServer.new(options)
        @server.mount "/", Rack::Handler::WEBrick, app
        yield @server  if block_given?
        @server.start
      end

	  ...

  	end
  end
end

WEBrick类是继承于 WEBrick::HTTPServlet::AbstractServlet 这个类的,在 self.run 方法中首先是 new 了一个 WEBrick::HTTPServer,server的mount方法 加载了WEBrick handler类,接下来server处理完block里面的内容后进行启动,看来所有的操作都是 WEBrick::HTTPServer 类完成的,下面就到这个类的源码里看看。

Webrick是ruby的标准函数库,不在gems目录里,我的机器上在 .rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/webrick 目录下面:

  • webrick/httpserver.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 HTTPServer < ::WEBrick::GenericServer
    def initialize(config={}, default=Config::HTTP)
      super(config, default)
      @http_version = HTTPVersion::convert(@config[:HTTPVersion])

      @mount_tab = MountTable.new
      if @config[:DocumentRoot]
        mount("/", HTTPServlet::FileHandler, @config[:DocumentRoot],
              @config[:DocumentRootOptions])
      end

      unless @config[:AccessLog]
        @config[:AccessLog] = [
          [ $stderr, AccessLog::COMMON_LOG_FORMAT ],
          [ $stderr, AccessLog::REFERER_LOG_FORMAT ]
        ]
      end

      @virtual_hosts = Array.new
    end

    ...
end

父类GenericServer主要做了一些config初始化,端口绑定的工作,mount serverlet 到 相应的路径。

然后看看 start 方法, httpserver里并没有start方法,而是调用了父类的start:

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
32
33
34
35
36
37
38
39
40
41
42
def start(&block)
      raise ServerError, "already started." if @status != :Stop
      server_type = @config[:ServerType] || SimpleServer

      server_type.start{
        @logger.info \
          "#{self.class}#start: pid=#{$$} port=#{@config[:Port]}"
        call_callback(:StartCallback)

        thgroup = ThreadGroup.new
        @status = :Running
        while @status == :Running
          begin
            if svrs = IO.select(@listeners, nil, nil, 2.0)
              svrs[0].each{|svr|
                @tokens.pop          # blocks while no token is there.
                if sock = accept_client(svr)
                  sock.do_not_reverse_lookup = config[:DoNotReverseLookup]
                  th = start_thread(sock, &block)
                  th[:WEBrickThread] = true
                  thgroup.add(th)
                else
                  @tokens.push(nil)
                end
              }
            end
          rescue Errno::EBADF, IOError => ex
            # if the listening socket was closed in GenericServer#shutdown,
            # IO::select raise it.
          rescue Exception => ex
            msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
            @logger.error msg
          end
        end

        @logger.info "going to shutdown ..."
        thgroup.list.each{|th| th.join if th[:WEBrickThread] }
        call_callback(:StopCallback)
        @logger.info "#{self.class}#start done."
        @status = :Stop
      }
end

start方法内通过@status 状态一直做循环监听,如果有请求就进入处理过程,在start_thread方法中调用httpserver 的 run方法, run方法种初始化了 HTTPResponseHTTPRequest 最后传递给了service方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def service(req, res)
  if req.unparsed_uri == "*"
    if req.request_method == "OPTIONS"
      do_OPTIONS(req, res)
      raise HTTPStatus::OK
    end
    raise HTTPStatus::NotFound, "`#{req.unparsed_uri}' not found."
  end

  servlet, options, script_name, path_info = search_servlet(req.path)
  raise HTTPStatus::NotFound, "`#{req.path}' not found." unless servlet
  req.script_name = script_name
  req.path_info = path_info
  si = servlet.get_instance(self, *options)
  @logger.debug(format("%s is invoked.", si.class.name))
  si.service(req, res)
end

servlet 变量就是之前在webrick handler里mount的类名,在这里取回来后通过 servlet.get_instance 创建了 Rack::Handler::WEBrick 实例, 最终实例调用自己的service方法,我们又回到了 webrick handler 里面:

1
2
3
4
5
def service(req, res)
  ...
  status, headers, body = @app.call(env)
  ...
end

在这里我们一开始传进去的app被调用到,并返回对应的内容,到此为止一个request通过rack 再到WebBrick的处理完成.

Carrierwave Uploader 中传递变量

在carrierwave uploader中经常会用到version的功能,一般我们的写法都是在里面写死process的一些参数,比如

1
2
3
  version :small do
    process :resize_to_fill => [48,48]
  end

但是有的时候可能我们需要动态的传递一些参数进来对图片做相应的操作,比如用户在前端选择图片的头像区域,然后传到后台进行切割,这个时候需要加一个process方法, 下面的crop_area方法通过参数来截取图片中对应的区域。

1
2
3
4
5
 def crop_area
    manipulate! do |img|
      ...
    end
  end

但是参数如何传进来成了问题,起初我是在 Uploader里定义了一个可访问的变量,然后在controller里将参数传递给Uploader实例,结果在crop_area里根本就访问不到Uploader里的实例变量,后来看了下源码发现carrierwave的实现逻辑貌似不能用这样的方法。于是用了一个曲线救国的方法,通过Model来传递参数:

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
32
33
# model class
# define attr_accessor coords
class User < ActiveRecord::Base
  attr_accessor :coords
  mount_uploader :icon, AvatarUploader
end



# controller
# pass the params to @user.coords
def crop_icon
  @user.coords = params[:coords]
  @user.icon = File.open(path)
  @user.save
  redirect_to :action=> 'basic'
end



# Uploader
# the model in the function is same as @user in controll,
# and can be invoked inside of process method 
 def crop_area
    manipulate! do |img|
      unless model.coords.nil?
        coords = JSON.parse(model.coords)
        img.crop("#{coords['w']}x#{coords['h']}+#{coords['x']}+#{coords['y']}")
      end
      img = yield(img) if block_given?
      img
    end
  end

所以最后通过在 Model 中设置实例变量解决动态传递参数的问题。

Rails 源码学习 1 (Rails 的初始化过程)

rails bin 文件

在rails 项目的目录里运行命令就会看到rails命令的路径/usr/bin/rails,其实是一个ruby文件,可以用编辑器打开看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env ruby
#
# This file was generated by RubyGems.
#
# The application 'railties' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = ">= 0"

if ARGV.first
  str = ARGV.first
  str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
  p str
  if str =~ /\A_(.*)_\z/
    version = $1
    ARGV.shift
  end
end

gem 'railties', version
load Gem.bin_path('railties', 'rails', version)

该文件做的事情主要是 require rubygems, 然后最后一行 load railties下面的rails,我们可以打印一下到底load了什么东西:

1
2
p Gem.bin_path('railties', 'rails', version)
# will out put /*/*/ruby-1.9.3-p194/gems/railties-3.2.7/bin/rails

然后就去看看 railties-3.2.7/bin/rails 里面都有什么吧,路径中的版本号只是我自己机器上使用的版本,可能和其他人的不一样,所以下面都会把路径中的版本号去掉。

railties/bin/rails

这里面也很简单,基本上只是设置环境,

1
2
3
4
5
6
7
#!/usr/bin/env ruby

if File.exists?(File.join(File.expand_path('../../..', __FILE__), '.git'))
  railties_path = File.expand_path('../../lib', __FILE__)
  $:.unshift(railties_path)
end
require "rails/cli"

最后require了 railties/lib/rails/cli.rb 这个文件:

railties/lib/rails/cli.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'rbconfig'
require 'rails/script_rails_loader'

# If we are inside a Rails application this method performs an exec and thus
# the rest of this script is not run.
Rails::ScriptRailsLoader.exec_script_rails!

require 'rails/ruby_version_check'
Signal.trap("INT") { puts; exit(1) }

if ARGV.first == 'plugin'
  ARGV.shift
  require 'rails/commands/plugin_new'
else
  require 'rails/commands/application'
end

第一行的rbconfig是ruby standlib, 可以用来知道当前环境ruby的path,第二行引用的就是rails的装载脚本了,下面的 Rails::ScriptRailsLoader.exec_script_rails! 方法就是在里面定义的

railties/lib/rails/script_rails_loader.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module Rails
  module ScriptRailsLoader
    RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
    SCRIPT_RAILS = File.join('script', 'rails')

    def self.exec_script_rails!
      cwd = Dir.pwd
      return unless in_rails_application? || in_rails_application_subdirectory?
      exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application?
	  ...	
    rescue SystemCallError
      # could not chdir, no problem just return
    end

    ...

  end
end

self.exec_script_rails 方法里就是真正的调用rails项目里 script/rails 这个文件啦, 这个文件在每个项目里都有的, 让我们再回到项目里看看这个文件

rails 项目中的 script/rails

1
2
3
4
5
6
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.

APP_PATH = File.expand_path('../../config/application',  __FILE__)
require File.expand_path('../../config/boot',  __FILE__)
require 'rails/commands'

这里require了 项目中的config/boot.rb 文件,它的主要作用是通过Bundle 把Gemfile 里的Gems都引用进来。最后一步 require 'rails/commands' 是真正更具我们输入的命令做执行的地方,比如 rails s,rails c, rails g 都是在这里做的判断。

railties/lib/rails/commands.rb

这里看看 启动server的片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
when 'server'
  # Change to the application's path if there is no config.ru file in current dir.
  # This allows us to run script/rails server from other directories, but still get
  # the main config.ru and properly set the tmp directory.
  Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru"))

  require 'rails/commands/server'
  Rails::Server.new.tap { |server|
    # We need to require application after the server sets environment,
    # otherwise the --environment option given to the server won't propagate.
    require APP_PATH
    Dir.chdir(Rails.application.root)
    server.start
  }

这里最主要的就是调用了 rails/commnads/server 该文件里定义了 server.start 方法。

railties/lib/rails/commands/server.rb

这个文件里主要是在 initialize的时候设置sever 的一些参数,再就是start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 def start
      url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
      puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
      puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
      puts "=> Call with -d to detach" unless options[:daemonize]
      trap(:INT) { exit }
      puts "=> Ctrl-C to shutdown server" unless options[:daemonize]

      #Create required tmp directories if not found
      %w(cache pids sessions sockets).each do |dir_to_make|
        FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make))
      end

      super
    ensure
      # The '-h' option calls exit before @options is set.
      # If we call 'options' with it unset, we get double help banners.
      puts 'Exiting' unless @options && options[:daemonize]
  end

这时你就知道在 rails s 的时候显示的几行文字是从这里打印出来的了,最后他会调用 Rack::Server.start 方法,也就是父类方法.

actionpack/lib/action_dispatch.rb

server.rb 里还require了 action_dispatch,这个文件在 Gem action_pack 目录下, 文件里引入了大部分rails需要的模块,包括:

1
2
3
4
5
6
require 'active_support'
require 'active_support/dependencies/autoload'

require 'action_pack'
require 'active_model'
require 'rack'

接下来就是rack server了

rack/lib/server.rb

rack 提供了统一的最简单的web server和应用程序之间的接口,所有的应用程序和web server都可以基于这个开发,大大节省了工作量和兼容问题,比如 rails 可以在自带的webserver webrick 跑,也可以在unicorn, thin,passanger 等webserver上跑,因为大家都是基于rack做的实现。

Java泛型简明教程

转自外刊it评论

泛型是Java SE 5.0中引入的一项特征,自从这项语言特征出现多年来,我相信,几乎所有的Java程序员不仅听说过,而且使用过它。关于Java泛型的教程,免费的,不免费的,有很多。我遇到的最好的教材有:

The Java Tutorial Java Generics and Collections, by Maurice Naftalin and Philip Wadler Effective Java中文版(第2版), by Joshua Bloch.

尽管有这么多丰富的资料,有时我感觉,有很多的程序员仍然不太明白Java泛型的功用和意义。这就是为什么我想使用一种最简单的形式来总结一下程序员需要知道的关于Java泛型的最基本的知识。
Java泛型由来的动机

理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:

1
2
List<Apple> box = ...;
Apple apple = box.get(0);

上面的代码自身已表达的很清楚:box是一个装有Apple对象的List。get方法返回一个Apple对象实例,这个过程不需要进行类型转换。没有泛型,上面的代码需要写成这样:

1
2
List box = ...;
Apple apple = (Apple) box.get(0);

很明显,泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换操作:编译器保证了这些类型转换的绝对无误。

相对于依赖程序员来记住对象类型、执行类型转换——这会导致程序运行时的失败,很难调试和解决,而编译器能够帮助程序员在编译时强制进行大量的类型检查,发现其中的错误。

泛型的构成

由泛型的构成引出了一个类型变量的概念。根据Java语言规范,类型变量是一种没有限制的标志符,产生于以下几种情况:

  • 泛型类声明
  • 泛型接口声明
  • 泛型方法声明
  • 泛型构造器(constructor)声明

泛型类和接口

如果一个类或接口上有一个或多个类型变量,那它就是泛型。类型变量由尖括号界定,放在类或接口名的后面:

1
2
3
public interface List<T> extends Collection<T> {
	...
}

简单的说,类型变量扮演的角色就如同一个参数,它提供给编译器用来类型检查的信息。

Java类库里的很多类,例如整个Collection框架都做了泛型化的修改。例如,我们在上面的第一段代码里用到的List接口就是一个泛型类。在那段代码里,box是一个List对象,它是一个带有一个Apple类型变量的List接口的类实现的实例。编译器使用这个类型变量参数在get方法被调用、返回一个Apple对象时自动对其进行类型转换。

实际上,这新出现的泛型标记,或者说这个List接口里的get方法是这样的:

1
T get(int index);

get方法实际返回的是一个类型为T的对象,T是在List声明中的类型变量。

泛型方法和构造器(Constructor)</strong>

非常的相似,如果方法和构造器上声明了一个或多个类型变量,它们也可以泛型化。

1
public static <t> T getFirst(List<T> list)

这个方法将会接受一个List类型的参数,返回一个T类型的对象。

你既可以使用Java类库里提供的泛型类,也可以使用自己的泛型类。 类型安全的写入数据… 下面的这段代码是个例子,我们创建了一个List实例,然后装入一些数据:

1
2
3
List<String> str = new ArrayList<String>();
str.add("Hello ");
str.add("World.");

如果我们试图在List装入另外一种对象,编译器就会提示错误:

1
str.add(1); // 不能编译

类型安全的读取数据…

当我们在使用List对象时,它总能保证我们得到的是一个String对象:

1
String myString = str.get(0);

遍历

类库中的很多类,诸如Iterator,功能都有所增强,被泛型化。List接口里的iterator()方法现在返回的是Iterator,由它的T next()方法返回的对象不需要再进行类型转换,你直接得到正确的类型。

1
2
3
4
for (Iterator<String> iter = str.iterator(); iter.hasNext();) {
    String s = iter.next();
    System.out.print(s);
}

使用foreach

“for each”语法同样受益于泛型。前面的代码可以写出这样:

1
2
3
for (String s: str) {
    System.out.print(s);
}

这样既容易阅读也容易维护。 ####自动封装(Autoboxing)和自动拆封(Autounboxing)

在使用Java泛型时,autoboxing/autounboxing这两个特征会被自动的用到,就像下面的这段代码:

1
2
3
4
5
6
7
8
List<Integer> ints = new ArrayList<Integer>();
ints.add(0);
ints.add(1);
 
int sum = 0;
for (int i : ints) {
    sum += i;
}

然而,你要明白的一点是,封装和解封会带来性能上的损失,所有,通用要谨慎的使用。

子类型

在Java中,跟其它具有面向对象类型的语言一样,类型的层级可以被设计成这样:

在Java中,类型T的子类型既可以是类型T的一个扩展,也可以是类型T的一个直接或非直接实现(如果T是一个接口的话)。因为“成为某类型的子类型”是一个具有传递性质的关系,如果类型A是B的一个子类型,B是C的子类型,那么A也是C的子类型。在上面的图中:

  • FujiApple(富士苹果)是Apple的子类型
  • Apple是Fruit(水果)的子类型
  • FujiApple(富士苹果)是Fruit(水果)的子类型
  • 所有Java类型都是Object类型的子类型。

B类型的任何一个子类型A都可以被赋给一个类型B的声明:

1
2
Apple a = ...;
Fruit f = a;

泛型类型的子类型

如果一个Apple对象的实例可以被赋给一个Fruit对象的声明,就像上面看到的,那么,List<Apple>List<Apple> 之间又是个什么关系呢?更通用些,如果类型A是类型B的子类型,那 C<A>C<B> 之间是什么关系? 答案会出乎你的意料:没有任何关系。用更通俗的话,泛型类型跟其是否子类型没有任何关系。 这意味着下面的这段代码是无效的:

1
2
List<Apple> apples = ...;
List<Fruit> fruits = apples;

下面的同样也不允许:

1
2
3
List<Apple> apples;
List<Fruit> fruits = ...;
apples = fruits;

为什么?一个苹果是一个水果,为什么一箱苹果不能是一箱水果?

在某些事情上,这种说法可以成立,但在类型(类)封装的状态和操作上不成立。如果把一箱苹果当成一箱水果会发生什么情况?

1
2
3
List apples = ...;
List fruits = apples;
fruits.add(new Strawberry());

如果可以这样的话,我们就可以在list里装入各种不同的水果子类型,这是绝对不允许的。

另外一种方式会让你有更直观的理解:一箱水果不是一箱苹果,因为它有可能是一箱另外一种水果,比如草莓(子类型)。
这是一个需要注意的问题吗?

应该不是个大问题。而程序员对此感到意外的最大原因是数组和泛型类型上用法的不一致。对于泛型类型,它们和类型的子类型之间是没什么关系的。而对于数组,它们和子类型是相关的:如果类型A是类型B的子类型,那么A[]是B[]的子类型:

1
2
Apple[] apples = ...;
Fruit[] fruits = apples;

可是稍等一下!如果我们把前面的那个议论中暴露出的问题放在这里,我们仍然能够在一个apple类型的数组中加入strawberrie(草莓)对象:

1
2
3
Apple[] apples = new Apple[1];
Fruit[] fruits = apples;
fruits[0] = new Strawberry();

这样写真的可以编译,但是在运行时抛出ArrayStoreException异常。因为数组的这特点,在存储数据的操作上,Java运行时需要检查类型的兼容性。这种检查,很显然,会带来一定的性能问题,你需要明白这一点。

重申一下,泛型使用起来更安全,能“纠正”Java数组中这种类型上的缺陷。

现在估计你会感到很奇怪,为什么在数组上会有这种类型和子类型的关系,我来给你一个《Java Generics and Collections》这本书上给出的答案:如果它们不相关,你就没有办法把一个未知类型的对象数组传入一个方法里(不经过每次都封装成Object[]),就像下面的:

1
void sort(Object[] o);

泛型出现后,数组的这个个性已经不再有使用上的必要了(下面一部分我们会谈到这个),实际上是应该避免使用。

通配符

在本文的前面的部分里已经说过了泛型类型的子类型的不相关性。但有些时候,我们希望能够像使用普通类型那样使用泛型类型:

*向上造型一个泛型对象的引用 *向下造型一个泛型对象的引用

向上造型一个泛型对象的引用

例如,假设我们有很多箱子,每个箱子里都装有不同的水果,我们需要找到一种方法能够通用的处理任何一箱水果。更通俗的说法,A是B的子类型,我们需要找到一种方法能够将 C<A> 类型的实例赋给一个 C<B> 类型的声明。

为了完成这种操作,我们需要使用带有通配符的扩展声明,就像下面的例子里那样:

1
2
List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;

“? extends”是泛型类型的子类型相关性成为现实:Apple是Fruit的子类型,List 是 List<? extends Fruit> 的子类型。 ####向下造型一个泛型对象的引用

现在我来介绍另外一种通配符:? super 。如果类型B是类型A的超类型(父类型),那么 C<B>C<? super A> 的子类型:

1
2
List<Fruit> fruits = new ArrayList<Fruit>();
List<? super Apple> = fruits;


为什么使用通配符标记能行得通?

原理现在已经很明白:我们如何利用这种新的语法结构?

? extends

让我们重新看看这第二部分使用的一个例子,其中谈到了Java数组的子类型相关性:

1
2
3
Apple[] apples = new Apple[1];
Fruit[] fruits = apples;
fruits[0] = new Strawberry();

就像我们看到的,当你往一个声明为Fruit数组的Apple对象数组里加入Strawberry对象后,代码可以编译,但在运行时抛出异常。

现在我们可以使用通配符把相关的代码转换成泛型:因为Apple是Fruit的一个子类,我们使用? extends 通配符,这样就能将一个 List<Apple> 对象的定义赋到一个 List<? extends Fruit> 的声明上:

1
2
3
List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
fruits.add(new Strawberry());

这次,代码就编译不过去了!Java编译器会阻止你往一个Fruit list里加入strawberry。在编译时我们就能检测到错误,在运行时就不需要进行检查来确保往列表里加入不兼容的类型了。即使你往list里加入Fruit对象也不行:

1
fruits.add(new Fruit());

你没有办法做到这些。事实上你不能够往一个使用了? extends的数据结构里写入任何的值。

原因非常的简单,你可以这样想:这个? extends T 通配符告诉编译器我们在处理一个类型T的子类型,但我们不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,我们就不允许往里面加入任何这种类型的数据。另一方面,因为我们知道,不论它是什么类型,它总是类型T的子类型,当我们在读取数据时,能确保得到的数据是一个T类型的实例:

1
Fruit get = fruits.get(0);

? super

使用 ? super 通配符一般是什么情况?让我们先看看这个:

1
2
List<Fruit> fruits = new ArrayList<Fruit>();
List<? super Apple> = fruits;

我们看到fruits指向的是一个装有Apple的某种超类(supertype)的List。同样的,我们不知道究竟是什么超类,但我们知道Apple和任何Apple的子类都跟它的类型兼容。既然这个未知的类型即是Apple,也是GreenApple的超类,我们就可以写入:

1
2
fruits.add(new Apple());
fruits.add(new GreenApple());

如果我们想往里面加入Apple的超类,编译器就会警告你:

1
2
fruits.add(new Fruit());
fruits.add(new Object());

因为我们不知道它是怎样的超类,所有这样的实例就不允许加入。

从这种形式的类型里获取数据又是怎么样的呢?结果表明,你只能取出Object实例:因为我们不知道超类究竟是什么,编译器唯一能保证的只是它是个Object,因为Object是任何Java类型的超类。

存取原则和PECS法则

总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:

如果你想从一个数据类型里获取数据,使用 ? extends 通配符 如果你想把对象写入一个数据结构里,使用 ? super 通配符 如果你既想存,又想取,那就别用通配符。
这就是Maurice Naftalin在他的《Java Generics and Collections》这本书中所说的存取原则,以及Joshua Bloch在他的《Effective Java》这本书中所说的PECS法则。

Bloch提醒说,这PECS是指”Producer Extends, Consumer Super”,这个更容易记忆和运用。
[本文英文原文链接:Java Generics Quick Tutorial ]

37signals倡导及实践的8个理念

转贴:以此为标准铭记于心

在软件设计界,37signals是非常受欢迎的一个小团队,对于Geek更是如此。不仅因为其自身产品的简洁、精益,更在于其对自身理念的实践。同时他们出版的书籍深受欢迎,特别是《Getting Real》及《Rework》。下面让我们看看他们所倡导的8个理念,对于产品设计、企业管理甚至个人素养都具有一定参考价值。
可用性才是永恒 其它一切都可能消失,唯可用性永远不会。我们只建立你需要的软件,其它一切都是多余。
优质的服务就是一切 我们以快速和友好的客户服务著称,每天都努力确保这份荣誉。
简洁清晰是王道 在37signals,口号、术语以及任何吹嘘式语言将无立足之地。我们的沟通讲求清晰与真诚。
客户就是投资者 我们的客户通过购买产品支持我们的日常运作,我们向他们汇报工作,而不是投资人、市场或者董事会。
基础的元素最美 我们从不会忽略真正重要的东西:基础元素、优质服务、易用性、可靠的价格以及对我们客户时间、金钱与信任的尊重。
定价公开 我们相信我们提供的价格是公道的,所有的价格面向所有人公开。
软件需要简单容易 我们的产品都具有直觉性,打开软件几分钟就可上手,而非花上几天甚至几周时间。我们不会给你培训而收费,因为你根本不需要。
长期合作是可憎的 没有人愿意将自己绑在一件不喜欢的事情上。我们的客户可以随时取消合作。

转自36氪 37signals倡导及实践的8个理念