Vagrant和Chef建Rails开发环境

Host环境: Yosemite, 使用的虚拟机VirtualBox 3.1.16

SetUp Vagrant

Vagrant会跑一个完整的虚拟机,所以你的机器最好有1,2G的空闲内存。

安装Vagrant和VirtualBox

再给Vagrant安装两个插件

  • vagrant-vbguest: 把host机器上的VirtualBox Guest Additions自动安装到虚拟机上
  • vagrant-librarian-chef: 自动跑Chef
1
2
vagrant plugin install vagrant-vbguest
vagrant plugin install vagrant-librarian-chef

添加镜像

由于国内的网速比较慢,所以还是先把ubuntu镜像单独下载下来,再安装! vagrantbox下载,假设下载到~/downloads/ubuntu-14.box, 在Terminal里输入

1
vagrant box add ubuntu14 ~/downloads/ubuntu14.box

‘ubuntu14’是给这个box去的名字,后面的是文件的路径

初始化开发环境

创建一个项目目录,比如~/documents/workspace/rails-vagrant, 进入目录,使用’ubuntu14’这个box

1
2
vagrant init ubuntu14
vagrant up

等待启动完成后, vagrant ssh 登录虚拟机了!

Vagrant建立的只是一台空白的虚拟机,离开发环境还远着。这时就需要Chef了

Chef

在目录下建立一个文件Cheffile, 然后编辑

1
2
3
4
5
6
7
8
9
10
site "http://community.opscode.com/api/v1"

cookbook 'apt'
cookbook 'build-essential'
cookbook 'mysql', '5.5.3'
cookbook 'ruby_build'
cookbook 'nodejs', git: 'https://github.com/mdxp/nodejs-cookbook'
cookbook 'rbenv', git: 'https://github.com/fnichol/chef-rbenv'
cookbook 'vim'
cookbook 'postgresql', '~> 3.4.10'

在虚拟机里装上编译需要的build-essential,rubymysqlpostgresql两个数据库(按自己需要可以选装)

编辑Vagrantfile

虚拟机的配置都在Vagrantfile里,

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  # Every Vagrant virtual environment requires a box to build off of.
  config.vm.box = "ubuntu-14"

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  config.vm.network "forwarded_port", guest: 80, host: 8080
  config.vm.network "forwarded_port", guest: 3000, host: 3000

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  config.vm.network "private_network", ip: "192.168.33.10"

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  config.vm.synced_folder "./codes", "/vagrant_data", :nfs => true

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  config.vm.provider "virtualbox" do |vb|
      # Don't boot with headless mode
      vb.gui = false
      # Use VBoxManage to customize the VM. For example to change memory:
    vb.customize ["modifyvm", :id, "--memory", "2048"]
  end


  # Enable provisioning with Puppet stand alone.  Puppet manifests
  # are contained in a directory path relative to this Vagrantfile.
  # You will need to create the manifests directory and a manifest in
  # the file default.pp in the manifests_path directory.
  #
  # config.vm.provision "puppet" do |puppet|
  #   puppet.manifests_path = "manifests"
  #   puppet.manifest_file  = "default.pp"
  # end

  # Enable provisioning with chef solo, specifying a cookbooks path, roles
  # path, and data_bags path (all relative to this Vagrantfile), and adding
  # some recipes and/or roles.
  #
  # Use Chef Solo to provision our virtual machine
  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = ["cookbooks", "site-cookbooks"]

    chef.add_recipe "apt"
    chef.add_recipe "nodejs"
    chef.add_recipe "ruby_build"
    chef.add_recipe "rbenv::user"
    chef.add_recipe "rbenv::vagrant"
    chef.add_recipe "vim"
    chef.add_recipe "mysql::server"
    chef.add_recipe "mysql::client"
    chef.add_recipe "postgresql::server"
    chef.add_recipe "postgresql::client"
    chef.add_recipe "postgresql::ruby"

    # Install Ruby 2.1.2 and Bundler
    # Set an empty root password for MySQL to make things simple
    chef.json = {
      rbenv: {
        user_installs: [{
          user: 'vagrant',
          rubies: ["2.1.2"],
          global: "2.1.2",
          gems: {
            "2.1.2" => [
              { name: "bundler" }
            ]
          }
        }]
      },
      mysql: {
        server_root_password: ''
      },
      postgresql: {
        password: {
          postgres: ""
        }
      },
      run_list: ["recipe[postgresql::server]"]
    }
  end
end

修改了Cheffile和Vagrantfile后,需要执行vagrant provision,修改才会生效。 执行时间可能比较长,等执行完之后,依旧执行vagrant ssh登录虚拟机。

这时的虚拟机才是安装了Ruby的开发环境Ready的机器。

参考

https://gorails.com/guides/using-vagrant-for-rails-development http://segmentfault.com/blog/fenbox/1190000000264347

如何最优化Nginx配置

Nginx 安装完了,那么如何配置? 多少个 Worker Processes? 多少个 Worker Connections?

多少个 Worker Processes

Worker Process数决定了Web Server跑多少个Nginx工作进程,因此CPU是几核的就配置几个,是合适的做法

1
grep processor /proc/cupinfo | wc -l

假设结果是 4, 那就设置4个 Worker Processes.

多少个Worker Connections

Worker Connections决定一个Process能同时server多少个用户, 默认值是768, 由于现在一个用户开启一个Session时,都会有好几个请求,因此这个值可以选择除以 2 或者 3. 我们可以通过检查cpu的limit,

1
ulimit -n

如果 返回结果 1024, 那么 worker connections的数数就是 1024 * 4 = 4096

Buffers

client_body_buffer_size: 这个决定客户端Post Request的Body的缓冲大小,如果body内容大于缓冲容量,整个正文或者一部就会写入临时文件。

client_header_buffer_size : 这个决定客户端request的header的大小

client_max_body_size: 顾名思义,body最大的size,超过这个大小,就会返回413错误

timeout

client_header_timeout: 如果客户端在这段时间内没有传送完整的头部到nginx, nginx将返回错误408 (Request Time-out)到客户端。

client_body_timeout: 定义读取客户端请求正文的超时。超时是指相邻两次读操作之间的最大时间间隔,而不是整个请求正文完成传输的最大时间。 如果客户端在这段时间内没有传输任何数据,nginx将返回408 (Request Time-out)错误到客户端

Compile Nginx with Page Speed Module

要安装的几个模块

  • SPDY: Nginx实验性的支持SPDY, 但是默认是不开启的,只要开启就好
  • Google Page Speed: 主要目的,Page Speed模块自动提供各种’压缩和优化,提高网站的性能
  • Headers More: 自定义Server信息
  • Naxsi: 提供防火墙功能。

开始前安装几个会用到的工具包

1
sudo apt-get install gcc-c++ pcre-dev pcre-devel zlib-devel make

安装

安装PageSpeed可以参考 Google 的 Page Speed 安装指南,

nginx的版本是1.6.0, cd nginx-1.6.0, 下载headers morenaxsi

在nginx目录里,下载解压,然后configure

有一点要注意,Naxsi的Wiki页面里提到,由于NGINX会根据module申明的顺序来排序,所以Naxsi需要排在第一位,不然可能会出现不可预知的错误

NGINX will decide the order of modules according the order of the module’s directive in nginx’s ./configure. So, no matter what (except you really know what you are doing) put naxsi first in your ./configure. If you don’t do so, you might run into various problems, from random / unpredictable behaviors to non-effective WAF.

PS: 注意修改NPS_VERSION

编译,安装

1
2
make
sudo make install

随系统自动启动Nginx

可以使用这个init.sh

reinstall mysql on mac with homebrew

测底删除MySQL

remove all related files
1
2
3
4
5
6
7
8
9
10
11
12
#kill all processes
ps aux | grep mysql

brew remove mysql
brew cleanup
sudo rm /usr/local/mysql
sudo rm -rf /usr/local/var/mysql
sudo rm -rf /usr/local/mysql*
sudo rm ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist
sudo rm -rf /Library/StartupItems/MySQLCOM
sudo rm -rf /Library/PreferencePanes/My*
launchctl unload -w ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist

edit /etc/hostconfig and remove the line MYSQLCOM=-YES-

1
2
3
4
rm -rf ~/Library/PreferencePanes/My*
sudo rm -rf /Library/Receipts/mysql*
sudo rm -rf /Library/Receipts/MySQL*
sudo rm -rf /private/var/db/receipts/*mysql*

重启机器,确保mysql已经删除干净了

用Brew安装

install mysql
1
2
3
4
5
6
7
brew doctor
brew update
brew install mysql

upset TMPDIR

mysql_install_db --verbose --user=whoami--basedir="$(brew --prefix mysql)" --datadir=/usr/local/var/mysql --tmpdir=/tmp

配置MySQL

配置默认编码为Unicode等,

configure my.cnf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[client]
port = 3306
socket = /tmp/mysql.sock
default-character-set = utf8

[mysqld]
collation-server = utf8_unicode_ci
character-set-server = utf8
init-connect ='SET NAMES utf8'
max_allowed_packet = 64M
bind-address = 127.0.0.1
port = 3306
socket = /tmp/mysql.sock
innodb_file_per_table=1

[mysqld_safe]
timezone = '+08:00'

初始化DB

init database
1
2
unset TMPDIR
mysql_install_db --verbose --user=`whoami` --basedir="$(brew --prefix mysql)" --datadir=/usr/local/var/mysql --tmpdir=/tmp

Final Step

start
1
2
mysql.server start
mysql_secure_installation

更多帮助,可以执行brew info mysql

Render No Layout Template for Non-Html Request

在 Rails 应用中,action 的返回的render对象都会套用 layout。这对于普通的 html 请求和 json数据请求都没有问题。html请求需要layout,而 json 数据请求直接 render 数据,不会套用 layout,那么对于普通的 ajax 请求呢?

destroy.js.erb?

对于普通的RJS请求,通常都不需要走layout,那么需要在每一个respond中设置吗?

1
format.js { layout: false }

想要设置所有的rjs请求没有layout,可以把layout设置为一个 Proc。 设置基类的layout

1
layout proc { |c| c.request.xhr? ? false : application }

Common Missing Packages for Installing Ruby Gems on Ubuntu

Gem Install Curb

1
2
3
sudo apt-get install libcurl4-openssl-dev
or
sudo apt-get install libcurl4-gnutls-dev

Gem Install RMagick

1
sudo apt-get install imagemagick libmagickwand-dev

Gem Install Mysql2

1
sudo apt-get install libmysql-ruby libmysqlclient-dev

你不知道的Rails Console Tips

清理Console

当输出满屏时,想清理一下,在bash里可以用clear,在rails console里,可以用command+k

重新加载rails环境

console环境不会自动加载修改后的文件,怎么办?退出重启?不需要!执行 reload!

搜索历史纪录

执行的command太多,往上可以用 uparrow, 往下可以用downarrow。但是当执行的命令太多时,上下翻历史记录耗费的时间,比直接输入来的更多。其实console有搜索功能,Ctrl+R

1
2
3
4
[1] pry(main)> reload!
Reloading...
=> true
(reverse-i-search)`r': reload!

输入r出来 reload! . 第一个匹配的记录

Tab补全

tab补全,属性bash的应该对这个都不陌生。

上一个命名的结果

执行完一条命令

1
>>Article.first

但其实你还想对这个返回的 article 对象继续操作。在执行一遍?

1
>>article = Article.first

NO! 你可以用, article = ; _保存着上一条命令返回的结果

发起HttpRequest

1
2
3
4
>>app.get /
=> 200
>>app.get /orders
=> 302

Rails沙盒

console可以已沙盒的模式运行,rails console —sandbox

1
2
3
>>User.destroy(1)
>>exit
     (0.1ms) rollback transaction

Rails环境

想要test环境的console,除了RAILS_ENV=test rails c, 更简单的是rails c test

Core Data中如何取得聚合值

Core Data是现在iOS中主流的数据存储选择。虽然Core Data的学习对于Mac/iOS开发新手来说,算是学习曲线比较陡的,但是一旦熬过了,使用还是很便利的。

各种介绍文章都会介绍如何设置NSPredicate, 如何设置NSSortDescriptor, 返回NSManagedObject对象。那么如何获得某个属性的最小值,最大值?

对于刚学习Core Data的人想到的第一方法很可能就是设置fetchLimit为1, 然后根据要获取的那个属性进行排序。这也是一种方法,也能完成任务;但是如果要获取这个属性的Sum值呢?

NSExpression

Core Data中有NSExpression来完成Aggregate操作。

首选需要设置你的目标属性, 比如salary

NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:@“salary”];

然后设置想要的聚合操作

1
2
NSExpression *maxSalaryExpression = [NSExpression expressionForFunction:@"max:"
                                                  arguments:@[keyPathExpression]];

再设置NSExpressionDescription

1
2
3
4
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:@"maxSalary"];
[expressionDescription setExpression:maxSalaryExpression];
[expressionDescription setExpressionResultType:NSDecimalAttributeType];

Objective-c就是以他的verbose闻名的。

1
2
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];

设置request的返回类型是一个Dictionary,这个Dictionary的key就是这个expressionDescription的name, value就是expression的执行结果。

最后再调用executeFetchRequest

1
2
3
4
5
6
NSArray *results = [objectContext executeFetchRequest:request error:&error];
if (!results || [results count] == 0) {
    return NSIntegerMax;
}

NSNumber *maxSalary = (NSNumber *)[[results lastObject] valueForKey:@"maxSalary"];

对于如何执行Sum操作,基本一样。

So,试试NSExpression,别再使用fetchLimit=1 了。

用Mantle转JSON数据到ManagedObject

Mantle, 来自github的一个十分便利的model层框架,它能把数据从JSON转成Objective-c对象,也可能把一个MTLModel对象转成JSON数据。

Mantle的官方说明中, 在基本的情况下,只需要实现一个mapping方法,就能轻松的实现数据的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 + (NSDictionary *)JSONKeyPathsByPropertyKey {
     return @{
         @"URL": @"url",
         @"HTMLURL": @"html_url",
         @"reporterLogin": @"user.login",
         @"assignee": @"assignee",
         @"updatedAt": @"updated_at"
     };
 }

//转换为NSURL类型
 + (NSValueTransformer *)URLJSONTransformer {
     return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
 }

 + (NSValueTransformer *)HTMLURLJSONTransformer {
     return [NSValueTransformer  valueTransformerForName:MTLURLValueTransformerName];
 }

然后用+[MTLJSONAdapter modelOfClass:fromJSONDictionary:error:]就能解析JSON数据,得到Objective-C对象了。

不过这不是写这篇文章的目的,这篇文章的重点是如何再把这个对象存到Core Data中。

对于持久化,Mantle的官方文档紧紧提到:

Mantle doesn’t automatically persist your objects for you. However, MTLModel does conform to , so model objects can be archived to disk using NSKeyedArchiver. If you need something more powerful, or want to avoid keeping your whole model in memory at once, Core Data may be a better choice.

只说了要想实现Core Data比NSCoding更好用,没说怎么用;但是其实Mantle已经支持把从JSON反序列化得到的对象存到Core Data里了。

实现+ (NSDictionary )managedObjectKeysByPropertyKey + (NSString )managedObjectEntityName 方法,前者负责从对象到数据库字段的mapping,后者说明Core Data中Entity对象的名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 + (NSDictionary *)managedObjectKeysByPropertyKey {
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];

    [dictionary setObject:@"name" forKey:@"name"];
    [dictionary setObject:@"address" forKey:@"address"];
    [dictionary setObject:@"days" forKey:@"daysOfWeek"];

    [dictionary setObject:@"startDate" forKey:@"startDate"];
    [dictionary setObject:@"endDate" forKey:@"endDate"];
    [dictionary setObject:@"location" forKey:@"location"];

    // ignore these two properties
    [dictionary setObject:[NSNull null] forKey:@"hoursOfOperation"];
    [dictionary setObject:[NSNull null] forKey:@"datesOfOperation"];

    return dictionary;
}
 + (NSString *)managedObjectEntityName {
    return @“NNStore;
}

然后再先从JSON转到Model对象,再存到core data中

1
2
3
4
5
6
7
8
9
10
11
NSArray *markets = [json valueForKeyPath:@“stores];
[markets enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {
    MFMarketModel *market = [MTLJSONAdapter modelOfClass:[MFMarketModel class]
                                      fromJSONDictionary:obj
                                                   error:NULL];
    NSManagedObject *managedMarket = [MTLManagedObjectAdapter managedObjectFromModel:market
                                                                insertingIntoContext:self.managedObjectContext
                                                                               error:NULL];
}];

[self.managedObjectContext save:nil];

Mantle还支持unique检查,实现+ (NSSet *)propertyKeysForManagedObjectUniquing就可以做到唯一性检查,非常简单。

如果你还没有用过,那就赶紧试试吧。

如何更新升级Octopress

为什么要更新?用着好好的。新版本可能会修正bug,提高性能;其实最重要的是,作为一个Geek,就是想要用最新版的而已。

How

找到官方文档,Updating Octopress, 按照指示

updating octopress
1
2
3
4
git pull octopress master     # Get the latest Octopress
 bundle install                # Keep gems updated
 rake update_source            # update the template's source
 rake update_style             # update the template's style

才执行第一句,就出错了

error
1
2
fatal: 'octopress' does not appear to be a git repository
fatal: Could not read from remote repository.

什么情况? 找不到名为Octopress的repository! 按照官方的设置,标准的Octopress有两个Branch, 一个Source,一个Master, Source分支上有你的源文件;Master就是最终生成的站点文件。 确实找不到Octopress 仓库,^_^

Add Remote Octopress Reository

git remote add octopress https://github.com/imathis/octopress.git

再执行官方文档给出的ruby语句,这下没问题了。