参考
最后更新于:2022-04-01 02:43:08
# 参考
### 官方资料
-
[Chef Wiki](https://wiki.opscode.com/display/chef/Home) ([https://wiki.opscode.com/display/chef/Home](https://wiki.opscode.com/display/chef/Home))
-
[GetChef Docs](http://docs.getchef.com/) ([http://docs.getchef.com/](http://docs.getchef.com/))
-
[chef-11-server-up-and-running](http://www.getchef.com/blog/2013/03/11/chef-11-server-up-and-running/) ([http://www.getchef.com/blog/2013/03/11/chef-11-server-up-and-running/](http://www.getchef.com/blog/2013/03/11/chef-11-server-up-and-running/))
-
[cookbooks](https://supermarket.getchef.com/cookbooks) ([https://supermarket.getchef.com/cookbooks](https://supermarket.getchef.com/cookbooks))
### 推荐相关书籍
- PacktPub Chef Infrastructure Automation Cookbook
- Test-Driven Infrastructure with Chef
- Customing Chef
### 互联网资料
Google搜Chef关键字,中英文所有能搜到的资料。
### Ruby
「Ruby Style」[https://github.com/JuanitoFatas/ruby-style-guide/blob/master/README-zhCN.md](https://github.com/JuanitoFatas/ruby-style-guide/blob/master/README-zhCN.md)
Chef Rest API
最后更新于:2022-04-01 02:43:05
# Chef Server Rest API
Chef11 中的核心api用erlang重写了,我们这里暂时不去深入探究erlang的代码,但是我们应该知道,erlang是性能的保证。erlang天生就是处理高并发的高手,chef server使用erlang来开发核心Api,我们几乎不用担心Chef Server的性能问题了。
Chef Server还使用了Rabbitmq来提供消息队列,来处理search服务,更是为了性能所考虑,消息队列有诸多好处,增加请求吞吐量、保证健壮性请求不易丢失等。
我们举个例子来说明下Chef Server Rest API:
![res-api](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-23_55d9d7af05034.png)
![res-api](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-23_55d9d7af20023.png)
我们可以看得出来, chef server rest api,跟我们Rails基础中讲过的restful概念,是一样的。
### 调用 Chef 命令
为了实现上层系统调用 Chef 的完全自动化,有时候需要自动化 Chef Client 的配置。将一个非 Chef Client 的普通机器自动注册成 Chef Client,能提高整个系统的自动化程度。Chef 的 REST API 没有提供注册 Chef Client 的功能,而 Chef 的 bootstrap 命令是用来完成这个工作的。所以上层系统需要使用某些机制(如使用 JSch)来在 Chef Workstation 上运行 bootstrap 命令。
通常我们用如下命令来将一个普通机器注册成 Chef Node:
~~~
knife bootstrap client_IP -x username -P password
~~~
如果客户端已经安装了 chef-client软件,此命令会直接将这个客户端注册成一个 Chef Node;如果客户端没有安装 chef-client软件,此命令会试图从网络上直接下载安装包去安装 chef-client 然后注册。
对于没有外部网络连接的客户端,又没有安装 chef-client软件,我们可以自定义 bootstrap 所用的模板,让其不从网络下载chef-client,而直接从本地服务器下载 chef-client 进行安装(前提是配置一个本地服务器如 HTTP 服务器,将 chef-client 软件预先放到服务器上)。
如果是默认安装,bootstrap 所用的模板位于 Workstation 的/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.10.4/lib/chef/knife/bootstrap/目录下。我们可以在此目录下新建一个模板,命名为 ubuntu12.04-gems-mine,在原有的 ubuntu12.04-gems 模板基础上进行修改,如将安装 chef-client 软件的部分自定义:
~~~
if [ ! -f /usr/bin/chef-client ]; then
mkdir -p /tmp/chef-installer
cd /tmp/chef-installer
#Copy chef client rpm from HTTP file server
wget http://http_server_ip/chef/chef-11.12.2-1.el6.x86_64.rpm
rpm -ivh chef-*.rpm
rm -rf /tmp/chef-installer
fi
~~~
然后我们使用自定义的模板来运行 bootstrap 命令:
~~~
# knife bootstrap client_ip -x username -P password –d ubuntu12.04-gems-mine
~~~
这样在 bootstrap 一个普通的没有安装 chef-client 的机器时,Chef 就会从本地的服务器上下载 chef-client 软件(无需外部网络连接),安装在客户机上,然后注册成 Chef Node。可见,对于没有提供 Chef REST API 的一些特殊 Chef 功能,可以通过调用 Chef 的命令行来完成。
### 结语
Chef Rest API, 和Rails的restful架构是一样的概念,我们通过调用 Chef 的 REST API,就可以完成对 Chef 资源的管理。而对于Rest Api并没有提供的功能,我们可以直接使用knife来调用chef命令来完成我们的工作。
Chef Server Webui组织结构
最后更新于:2022-04-01 02:43:03
# Chef Server WebUI
WebUI的组织结构和Rails基础讲的类似。 唯一的区别就是:
Chef Server WebUI 没有直接操作数据库,所以config下面没有相关数据库配置。
因为都是通过erchef提供的 restful api来和数据库通信。
### node示例
拿我们在第二章应用中创建的node2来示例。 打开chef server webui: [http://local.chef.com](http://local.chef.com) (这是我本地/etc/hosts里设置的测试域名)
![webui-demo-node](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-23_55d9d7aede8ee.png)
在登陆之后,我们点击nodes。默认的日志在「/var/log/chef-server/chef-server-webui/current」目录下面:
~~~
$ sudo tail -f /var/log/chef-server/chef-server-webui/current
~~~
你可以观察日志,当导航栏nodes被点击的时候:
~~~
Started GET "/nodes" for 127.0.0.1
...
...
~~~
日志中我们看到,是对/nodes发起了GET请求, 这个/nodes是啥呢? 我们之前Rails基础里讲过了, 这个是resources,我们去controller里面去找,根据Rails基础中提过的约定大于配置,就应该是在app/controllers/nodes_controller.rb中定义的。
~~~
require 'chef/node'
class NodesController < ApplicationController
respond_to :html
before_filter :require_login
before_filter :require_admin, :only => [:destroy]
def index
node_hash = if session[:environment]
client_with_actor.get("environments/#{session[:environment]}/nodes")
else
client_with_actor.get("nodes")
end
@node_list = node_hash.keys.sort
rescue => e
log_and_flash_exception(e, "Could not list nodes")
@node_list = {}
end
# ...
end
~~~
我们只摘出了nodes controller的部分代码, index action,就是GET /nodes所响应的那个action方法。可以通过config/routes.rb文件得到印证:
~~~
ChefServerWebui::Application.routes.draw do
resources :nodes, :id => /[^\/]+/
# ...
end
~~~
可以回头参考我们讲的Rails基础,就知道当我们GET /nodes的时候,就是响应index action。这就是restful。
回到我们的index action中:
这个action,要获取注册到这个chef server的所有nodes信息。这里最关键的方法是这个:
~~~
client_with_actor.get
~~~
而client_with_actor这是一个方法,被定义在lib / chef_server_webui / api_client_helper.rb文件中:
~~~
require 'chef_server/rest_client'
module ChefServerWebui
module ApiClientHelper
#
# We customize the behavior of Chef::REST in two important ways:
#
# 1. Set the 'x-ops-request-source' request header so all requests are
# authenticated as the webui user.
# 2. Set the client_name of *some* requests to that of the logged-in user
# so these requests are effectively authorized as said user.
#
DEFAULT_REQUEST_HEADERS = {
:headers => ChefServerWebui::Application.config.rest_client_custom_http_headers.merge({'x-ops-request-source' => 'web'})
}.freeze
# Returns an instance of ChefServer::RestClient with the 'actor' set to the
# webui client.
def client
client_with_actor(ChefServerWebui::Application.config.chef_server_url,
ChefServerWebui::Application.config.rest_client_name,
ChefServerWebui::Application.config.rest_client_key)
end
# Returns an instance of ChefServer::RestClient with the 'actor' set to the
# current logged in user. The current user is set in
# Thread.current[:current_user_id] by the Rails appliction using an
# around_filter
def client_with_actor(url=Rails.configuration.chef_server_url,
actor=Thread.current[:current_user_id],
signing_key_filename=ChefServerWebui::Application.config.rest_client_key)
ChefServer::RestClient.new(url, actor, signing_key_filename, DEFAULT_REQUEST_HEADERS)
end
end
end
~~~
这个文件代码很短,我就复制了全部在这里,我们可以看出来, 这个helper方法,是用于跟erchef核心api来交互的。client_with_actor方法最终返回的是一个ChefServer::RestClient对象。
我们再去看看这个ChefServer::RestClient类,在lib / chef_server / rest_client.rb里面被定义:
~~~
require 'chef/log'
require 'chef/rest'
require 'chef/run_list'
require 'forwardable'
module ChefServer
class RestClient
extend Forwardable
attr_reader :rest_client
def_delegator :@rest_client, :get_rest
def_delegator :@rest_client, :post_rest
def_delegator :@rest_client, :put_rest
def_delegator :@rest_client, :delete_rest
def_delegator :@rest_client, :fetch
def initialize(chef_server_url,
client_name,
signing_key_filename,
options={})
@rest_client = Chef::REST.new(chef_server_url,
client_name,
signing_key_filename,
options)
end
[:get, :post, :put, :delete].each do |method|
define_method method do |*args|
begin
@rest_client.send("#{method}_rest".to_sym, *args)
rescue => e
Chef::Log.error("#{e}\n#{e.backtrace.join("\n")}")
raise e
end
end
end
end
end
~~~
可以看得出来:
client_with_actor.get方法,实际就是ChefServer::RestClient.get, 而ChefServer::RestClient.get,实际是调用了Chef::REST.get,也就是我们前面所讲的chef gem里的chef/rest.rb里定义的方法。
### 小结
client_with_actor和erchef api交互,来获取nodes的相关信息。
这里只是用nodes作为示例,实际上webui中其他导航栏的信息,道理都是一样的,大家可以自己思考。
而重点在于erchef的核心api。
Chapter 6: Chef Server WebUI
最后更新于:2022-04-01 02:43:01
# Chef Server WebUI
本章主要介绍Chef Server WebUI的有关知识。Chef Server WebUI是用Ruby on Rails 3.2版本来写的,在我们前面讲过Rails的基础知识之后,应该可以好理解了。
Rails组成与项目结构
最后更新于:2022-04-01 02:42:58
# Rails组成与项目结构
Ruby on Rails框架是基于REST和MVC架构思想来设计的,那么让我们先看看Rails的组成结构,了解它是怎么实现这些思想的。
### Rails组成
当你用gem install rails命令安装Rails,你会发现它安装了很多的组件:
可以用gem list查看,这里把Rails核心组件列出来,其他的依赖就不说了。
~~~
actionmailer
actionpack
actionview
activemodel
activerecord
activeresource
activesupport
rails
railties
~~~
1.
actionmailer
专门提供email发送服务的一个框架。
1.
actionpack
包含了action dispatch ,action controller和 action view,用于Rails中控制器、路由和view层的交互。
1.
actionview
Rails4 中,从actionpack中独立出来,所以上面的actionpack在Rails4中只剩下action dispatch和action controller了。
1.
activemodel
独立于activerecord,处理model层的数据验证等逻辑,非数据存储,可以算是ORM的一个前端,你可以用它实现自己的ORM,比如开源的一个mongodb orm:mongoid
1.
activerecord
实现了零配置将Ruby类映射到数据库,我们用它来和数据库进行交互,它依赖于active model。
1.
activeresource
Rails4已经移除,Rails3中,用于消费REST api。
1.
activesupport
active support 中包含了一组工具类,以及对Ruby标准库的各种扩展,主要是为了给rails提供支持。 rails的其他组件都依赖于active support组件。
1.
rails & railties
上面的activerecord,actionpack,activesupport,activemodel,actionview,actionmailer都是gem形式存在,而rails是包含了它们这些组件的一个总的gem。
railties是用于粘合这些组件相互协同工作的组件,并且它还提供了生成器和rails的各种命令。
### 约定大于配置
Rails的一个先进理念,就是约定大于配置。也是Rails的核心原则,也是Raila开发效率提升的关键。 在Rails实现这个理念的若干年后,其他Web开发框架才反应过来,纷纷表示自己实现了约定大于配置。
那么约定大于配置在Rails中表现在哪?
-
数据库表名和model名单复数约定
比如: model是User, 那么Rails就会去找users表。 model是单数,数据库表是复数。
-
类名、模块名和文件查找
比如: 类名是User, 那么它一定对应文件名是user.rb。模块名是 Admin::User, 那么它一定对应于admin/user.rb文件。
通过这些约定,我们就省了不少配置,几乎达到了零配置。用JavaEE框架做过项目的朋友应该可以明白配置的痛苦。
-
controller会默认渲染对应名字的view
比如: controllers/users_controller 会自动的渲染 views/users.html.erb 这样的view文件。
-
RESTful: controller里面的action 默认响应HTTP 方法
比如:
- GET /users 会自动响应 index action
- PUT|PATCH /users/1 会自动响应 update action
- POST /users 会自动响应 create action
- DELETE /users/1 会自动响应 delete action
-
Migration 数据迁移脚本
比如:你创建一个数据迁移脚本 xxx_create_users.rb ,rails会根据这个名字帮你生成脚本文件。 稍后我们会见识到。
理解Rails的约定大于配置原则,对你Rails的学习有很大帮助。
### Rails项目结构
我们使用rails new 命令创建一个新项目:
~~~
rails new reading
~~~
你也可以用指定版本号来创建一个项目:
~~~
rails _3.2.18_ new reading
~~~
*当然,我们安装好Chef Server自动就已经有这个项目了。*
一个默认的Rails(3.2.18)项目的目录结构如下:
~~~
app/
|_ assets/
|_ controllers/
|_ helpers/
|_ mailers/
|_ models/
|_ views/
config/
|_ application.rb
|_ database.yml
|_ environments/
|_ locales/
|_ boot.rb
|_ environment.rb
|_ initializers/
|_ routes.rb
config.ru
db/
|_ seeds.rb
doc/
|_ README_FOR_APP
Gemfile
Gemfile.lock
.gitignore
lib/
|_ assets/
|_ tasks/
log/
public/
|_ 404.html
|_ 422.html
|_ 500.html
|_ robots.txt
Rakefile
README.rdoc
script/
|_ rails
test/
|_ fixtures/
|_ functional/
|_ integration/
|_ performance/
|_ test_helper.rb
|_ unit/
tmp/
|_ cache
vendor/
|_ assets/
|_ plugins/
~~~
Rails 4.1 的项目结构有所变化:
script目录变成了bin,config里多了secret.yml
~~~
app/
|_ assets/
|_ controllers/
|_ helpers/
|_ mailers/
|_ models/
|_ views/
bin/
|_ bundle
|_ rails
|_ rake
config/
|_ application.rb
|_ database.yml
|_ environments/
|_ locales/
|_ secrets.yml
|_ boot.rb
|_ environment.rb
|_ initializers/
|_ routes.rb
config.ru
db/
|_ seeds.rb
doc/
|_ README_FOR_APP
Gemfile
Gemfile.lock
.gitignore
lib/
|_ assets/
|_ tasks/
log/
public/
|_ 404.html
|_ 422.html
|_ 500.html
|_ robots.txt
Rakefile
README.rdoc
test/
|_ fixtures/
|_ functional/
|_ integration/
|_ performance/
|_ test_helper.rb
|_ unit/
tmp/
|_ cache
vendor/
|_ assets/
|_ plugins/
~~~
虽然Rails3和Rails4的目录结构有一点变化,但总的思想是不变的。
Chef11 的server webui目前是Rails3写的,所以我们看Rails3就够用了。
### app目录
-
app目录的作用是组织REST架构和MVC架构的相关代码。也是Rails项目启动文件默认加载的目录。
-
assets 目录:包含了前端的资源,javascript、css、images。
-
controllers目录:包含了所有的controller,在Rails中一般controller就是指REST架构中的资源。controller里实现了各种action,用于响应web请求。 也是MVC架构中的C层。
-
helpers目录: 用于存放一些helper方法,这些helper方法一般用在view层,用于组织一些用于view的逻辑代码。
-
mailers目录: 用于放和邮件发送相关的代码。
-
models目录: 用于放各种数据库映射的model,一些数据操作以及业务逻辑代码,都应该放在这里。 MVC架构中的M层。
-
views目录: 用于放views层的模板,经过controller渲染这些模板,最后生成可供用户访问的页面。MVC架构中的V层。
### bin目录 (或Rails3 中的script目录)
Rails的命令,以及bundle、rake命令。
### config目录
这里配置Rails项目的运行规则。
-
application.rb 文件,用于配置你的Rails项目运行的各种参照、状态。
-
database.yml 文件用于配置你的数据库连接驱动及信息。
-
boot.rb 用于load bunler环境,加载依赖gem
-
environment.rb 初始化Rails应用
environments目录,包含了Rails预设的三种状态,development、production、test。
这三种状态,对应不同的环境,development为开发环境,production为生产环境,test为测试环境。 我们开发的时候一般用development环境,部署到线下,需要用production环境, 做单元测试的时候,就是用测试环境。 这三种环境对应的rails应用的配置有所不同。
-
locales 这个目录包含用于国际化的yml配置文件。
-
secrets.yml 这个文件默认存有应用程序的 secret_key_base,也可以用来存放其它 secrets,比如存放外部 API 需要用的 access keys。
-
initializers 目录,这个目录存放着一些rails的启动文件,这些文件是rails默认对外开放,意味着你可以在里面添加一些东西,来改变rails的启动状态。你还可以在这个文件夹下面放置你自己的启动文件。
- routes.rb, 这个文件是Rails中最重要的一个文件之一,因为这个文件中指定了你项目所有的路由配置,也就是说,所有的web请求收发规则,都由这里指定。
config.ru 文件, rackup启动所需要的一个配置文件
### db 目录
这个目录存放的东西,主要是数据库相关。 seeds.rb 用于初始化应用数据。 将来用于创建数据库表结构的migration脚本文件,也会放到这个目录下面。 以及创建完数据库表生成的schema文件。
### Gemfile
这个是bundler工具提供的,管理项目中用到的gem依赖。Rails启动应用会先从这个文件中加载。
bundle install 命令,帮助你安装所有的gem依赖。 然后会生成一个Gemfile.lock文件。
### lib目录
用于存放第三方库。
tasks目录,用于存放rake 任务。
assets目录,用于存放我们自己代码库或者共用代码的静态资源。
### public
这个目录用于web服务器,而非应用服务器,这个目录作为web服务器的根目录。
### 其他目录
doc目录,用于存放项目的文档。
vendor目录,用于存放第三方资源,assets和plugins。
log目录, 日志输出的目录。
tmp 目录,用来存放暂时用途的文件,比如文件上传的临时文件等。
.gitignore 此文件用于配置不想或不能加入到git版本控制系统中的文件,比如database.yml , secrets.yml文件等。
Restful
最后更新于:2022-04-01 02:42:56
# Restful
Rails从1.2版本就开始实现RESTful了。
### 什么是REST(Representational State Transfer) 及 RESTFul
REST(Representational State Transfer)软件架构,是由Roy Thomas Fielding博士在2000年首次提出的。直到2005年,随着Ajax、Rails等Web开发技术的兴起,在Web开发技术社区掀起了一场重归Web架构设计本源的运动,REST架构风格得到了越来越多的关注。在2007年1月,支持REST开发的Ruby on Rails 1.2版正式发布,并且将支持REST开发作为Rails未来发展中的优先内容。Ruby on Rails的创始人DHH做了一个名为“World of Resources”的精彩演讲,DHH在Web开发技术社区中的强大影响力,使得REST一下子处在Web开发技术舞台的聚光灯之下。
直到今天,几乎看不到不支持REST开发的Web开发框架了。
所以,要想掌握Rails,REST是必须要理解的概念,除非你回去用Rails1.2之前的版本。
REST软件架构遵循了CRUD原则,该原则告诉我们对于资源(包括网络资源)只需要四种行为:创建(Create)、获取(Read)、更新(Update)和销毁(DELETE)就可以完成对其操作和处理了。
REST的特点有五个:
- 资源(Resource)
- 资源的表述(Representation)
- 状态转移(State Transfer)
- 统一接口(Uniform Interface)
- 超文本驱动(Hypertext Driven)
分别代表:
1.
资源: 是服务器的一种抽象,服务器是由很多离散资源所组成。资源以名词为核心来组织,一个资源可由一个或多个URI来标识,URI既是资源的名称,也是资源的地址。资源可以是系统上的文件、可以是数据库中的表等具体的东西,也可以说其他抽象的东西,只要开发者自己能理解。
1.
资源的表述: 资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。
1.
状态转移:是指在客户端和服务端之间通过转移代表资源状态的表述(json/html/xml等),来实现操作资源的目的。
1.
统一接口:REST要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。以HTTP/1.1协议为例,HTTP/1.1协议定义了一个操作资源的统一接口,主要包括以下内容:
7个HTTP方法:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS
1.
超文本驱动: 资源之间通过超链接相互关联,超链接既代表资源之间的关系,也代表可执行的状态迁移。
### 与REST架构风格并行的其他架构风格
-
分布式对象(Distributed Objects,简称DO) 架构实例有CORBA/RMI/EJB/DCOM/.NET Remoting等等
-
远程过程调用(Remote Procedure Call,简称RPC) 架构实例有SOAP/XML-RPC/Hessian/Flash AMF/DWR等等
### Rails中的REST实现
- Rails中的每个Controller被看作一个资源
- 资源的CRUD,Rails默认支持使用HTTP的GET/POST/PUT/DELETE
- 路由系统和URL Helper 方法生成基于RESTful的超链接
Rails中定义资源, config/routes.rb文件中:
~~~
resources :photos
~~~
Rails中action和http 方法的对应表:
| HTTP Verb | Path | action | used for |
|-----|-----|-----|-----|
| GET | /photos | index | 显示所有的photos资源 |
| GET | /photos/new | new | 返回一个创建新photo的页面 |
| POST | /photos | create | 创建一个新的photo资源 |
| GET | /photos/:id | show | 显示一个指定的photo资源 |
| GET | /photos/:id/edit | edit | 返回一个编辑指定photo资源的html页面 |
| PATCH/PUT | /photos/:id | update | 更新一个指定的photo资源 |
| DELETE | /photos/:id | destroy | 删除一个指定的photo资源 |
注: PATCH只有Rails4支持。
上面的表中,我们可以看到Rails的action对应的HTTP方法,是符合RESTful的。
再看看Rails提供的URL Helper方法:
- photos_path 生成的path: /photos
- new_photo_path 生成的path: /photos/new
- edit_photo_path(:id) 生成的path: /photos/:id/edit (例如, edit_photo_path(10) 生成的path: /photos/10/edit)
- photo_path(:id) 生成的path: /photos/:id (例如, photo_path(10) 生成的path: /photos/10)
### 小结:
RESTful架构,我自己的理解,用一句话简单的描述:
用HTTP本身提供的方法来响应服务器资源,并来修改其状态,就是RESTful的。
比如 GET [http://www.xxx.com/photos](http://www.xxx.com/photos) 就是要获取photos的所有资源。
非RESTFul的方式是这样的,拿RPC风格来举例: GET或POST请求, [http://www.xxx.com/get_photos](http://www.xxx.com/get_photos) 而这个get_photos是你自己定义的,别人也可以定义为get_all_photos,一点都不统一。并且忽略了HTTP方法本身的意义。
所以,RESTful,叫重归Web架构设计本源。
对于这个概念,也要求大脑里先有这个概念,后面我们再慢慢深入。
那么REST架构和MVC架构是否冲突?
我觉得不冲突, MVC是来描述系统内部的代码组织与数据流向,而REST架构,是一种Web Service接口规范。一个是内部,一个是外部。
了解Restful架构,对于我们理解Chef Server Api至关重要。
MVC架构
最后更新于:2022-04-01 02:42:54
# MVC架构
Rails是一个MVC框架。
### MVC全名是Model View Controller
是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
- Model(模型)表示应用程序核心(比如数据库记录列表)
- View(视图)显示数据
- Controller(控制器)输入和输出的路由,从页面取数据插入到相关的model,以及从model拿数据渲染相关的view
### Rails中的MVC实现
![mvc](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-23_55d9d7aec46a5.jpg)
看图,Rails的MVC是这样处理一个请求的:
1. 浏览器发出 Web 请求
1. 路由模块将请求信息发送给相应的控制器,由控制器决定如何处理请求;
1. 控制器根据请求处理逻辑,调用相应的模型来完成业务逻辑;
1. 根据实际需求,完成数据的检索或存储;
1. 控制器组织处理信息,调用视图解析从模型返回的数据; 完成页面渲染,返还数据给浏览器。
对于这个过程,暂时大脑里有个概念就可以了。后面我们会继续深入此概念。
Rails是什么
最后更新于:2022-04-01 02:42:52
# Rails是什么
### Rails是什么?
Rails的全称是Ruby on Rails,它是用Ruby实现的一个全栈Web开发框架。
它是第一个Ruby开发的杀手级框架,因为使用Ruby on Rails开发一个网站的开发效率是超级高的。
从2004年诞生到现在, 到今年,差不多已经10年了。Rails成就了硅谷很多互联网创业公司,包括大名鼎鼎的Twitter、程序员最喜欢的Github、Slidesshare、Speaker Deck、Groupon、Shopify、Tumblr、Kickstarter等知名网站。还有很多公司就不列举了。
到现在,Rails已经到4.1版本了,但是使用Rails3的项目也不少,因为版本的变化较大,所以有些项目还在继续使用Rails3而没有升级到Rails4。
我本人的建议是,使用Rails3的尽量升级到Rails4, 新项目就直接用Rails4开始就可以了。但是Chef Server WebUI还是用的Rails3,如果要二次开发的话,可以从Rails4开始。
本章的内容不分Rails的版本,因为不管Rails怎么升级,基本的架构思想是不怎么变化的。
### Web开发技术革新的领导者
Rails是当之无愧的Web开发前沿技术革新的领导者。
Web开发的各种前沿技术,你都可以结合Rails来享受,永不落伍。
- 第一个实现了Restful
- 约定大于配置的先进理念
- 默认支持HTML5、JQuery、coffeescript、sass等前沿工具
- 每一个Rails应用都可以方便的分布式扩展
- 强大的社区支持
还有很多优点,我都数不上来,没错,Rails就是当今的Web开发框架之王。
Chapter 5: Rails基础
最后更新于:2022-04-01 02:42:49
# Rails基础
在讲Chef Server WebUI之前,系统的了解一些Rails基础是非常有必要的。
Chef源码组织
最后更新于:2022-04-01 02:42:47
# Chef源码组织
我们这里讲的Chef源码,是指opscode发布在github的那个名为chef的项目 [chef in github: https://github.com/opscode/chef](https://github.com/opscode/chef)
chef本身就是一个gem,它的源码组织结构,首先是一个gem的组织结构:
### 组织结构
![chef-github](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-23_55d9d7ae7a4f0.png)
基本上Chef的组织结构是一个标准的gem包。
一个gem包的主要元素:
-
**lib/**
lib下面,是这个gem的核心代码
-
**.gemspec文件**
这里是这个gem的规格文件,里面包含了gem的作者信息、lib的目录位置、bin目录位置、运行时依赖包以及开发时依赖包等
-
**bin/**
如果你的gem有终端命令,那么就放到这个目录下面吧
-
**tasks/**
放置Rack任务的目录。
-
**spec/**
Rspec测试目录
-
**Gemfile**
bunlder的包管理文件。
回到我们的Chef源码目录里面:
### chef.gemspec
这是chef这个gem的规格文件,
~~~
Gem::Specification.new do |s|
s.name = 'chef'
s.version = Chef::VERSION
s.platform = Gem::Platform::RUBY
#...
s.required_ruby_version = ">= 1.9.3"
s.add_dependency "mixlib-config", "~> 2.0"
s.add_dependency "mixlib-cli", "~> 1.4"
s.add_dependency "mixlib-log", "~> 1.3"
s.add_dependency "mixlib-authentication", "~> 1.3"
s.add_dependency "mixlib-shellout", ">= 2.0.0.rc.0", "< 3.0"
s.add_dependency "ohai", ">= 7.6.0.rc.0"
#...
s.bindir = "bin"
#...
s.require_path = 'lib'
#...
end
~~~
一个gemspec文件,必须是按照这种格式来写。实际上是初始化了一个Gem::Specification类的对象。你也可以理解为,chef这个gem,就是一个gem对象。这个文件指定了chef这个gem的各种属性。
### bin
我们再来看下bin目录。 bin里面放置的是chef这个gem所包含的命令:
![chef-bin](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-23_55d9d7ae99732.png)
我们看到chef常用的几个命令了:chef-client、knife、chef-shell、chef-solo等。我们打开chef-client文件看看:
~~~
require 'rubygems'
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
require 'chef'
require 'chef/application/client'
Chef::Application::Client.new.run
~~~
再打开knife文件看看:
~~~
require 'rubygems'
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
require 'chef/application/knife'
Chef::Application::Knife.new.run
~~~
~~~
require 'rubygems'
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
require 'chef/application/apply'
Chef::Application::Apply.new.run
~~~
我们依次打开这个几个bin文件看了下,除了chef-shell和shef文件之外,结构基本都差不多, 都和上面的代码类似,都是加载了rubygems, 在加载路径里面添加了lib目录, 然后又把lib/chef/applications/下面的相关文件加载,最后用了几乎统一的方法:
~~~
Chef::Application::Xxx.new.run
~~~
我们学过Ruby的基础知识,可以看得出来,Chef::Application::Xxx肯定是被定义在 chef/application/目录下面的Xxx类。
而chef-shell和shef文件,我们打开后,惊异的发现,这俩文件中代码几乎一模一样:
chef-shell:
~~~
begin
require "rubygems"
rescue LoadError
end
require "irb"
require "irb/completion"
require 'irb/ext/save-history'
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
require "chef/shell"
# On Windows only, enable irb --noreadline because of input problems --
# See CHEF-3284.
IRB.conf[:USE_READLINE] = false if Chef::Platform::windows?
Shell.start
~~~
shef:
~~~
begin
require "rubygems"
rescue LoadError
end
require "irb"
require "irb/completion"
require 'irb/ext/save-history'
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
require "chef/shell"
Chef::Log.warn("DEPRECATED: The 'shef' program is renamed to 'chef-shell'")
Shell.start
~~~
当我们看到shef中倒数第二行代码的时候:
~~~
Chef::Log.warn("DEPRECATED: The 'shef' program is renamed to 'chef-shell'")
~~~
可以看得出来,Chef::Log.warn ,应该是Chef的日志输出,warn代表一个警告,里面的字符串意思是:过期声明: 这个shef程序已经被重命名为chef-shell了。
好了,我们已经破案了。 只需要看chef-shell就好了。
chef-shell代码中,require了irb及其组件,并且也加载了chef/shell文件,最后启动了Shell.start命令。
chef-shell命令,实际上是开启了一个加载了chef环境的irb交互shell界面。我们在安装了chef的终端输入这个命令就知道了:
~~~
$ chef-shell
loading configuration: none (standalone session)
Session type: standalone
Loading.....done.
This is the chef-shell.
Chef Version: 11.16.0
http://www.opscode.com/chef
http://docs.opscode.com/
run `help' for help, `exit' or ^D to quit.
Ohai2u vagrant@chef-node!
chef >
~~~
你可以输入help查看帮助。
### lib
我们通过查看bin下面的文件,了解到,一个chef命令的执行,必须先加载lib/chef下面的相关文件,那么我们就去lib目录下面看看。
lib目录下面的文件组织也是有规范的:
![chef-lib](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-23_55d9d7aeb0306.png)
这样的组织结构,意味着,当我们使用这个gem的时候,直接:
~~~
require 'chef'
~~~
就可以了。 require 'chef', 这个命令会直接加载chef gem lib下面的chef.rb,我们看看chef.rb中的代码:
~~~
require 'chef/version'
require 'chef/nil_argument'
require 'chef/mash'
require 'chef/exceptions'
require 'chef/log'
require 'chef/config'
require 'chef/providers'
require 'chef/resources'
require 'chef/shell_out'
require 'chef/daemon'
require 'chef/run_status'
require 'chef/handler'
require 'chef/handler/json_file'
require 'chef/monkey_patches/tempfile'
require 'chef/monkey_patches/string'
require 'chef/monkey_patches/numeric'
require 'chef/monkey_patches/object'
require 'chef/monkey_patches/file'
require 'chef/monkey_patches/uri'
~~~
chef.rb中就是一堆require, 加载了lib/chef目录下的全部文件。
所以,chef.rb,算是一个入口。核心的代码还在chef目录下面。
但是我们从chef.rb文件中加载的文件名称中,也可以看出chef整体架构的一个大概。
chef分为四部分,由空行来分隔:
~~~
#第一部分:
require 'chef/version'
require 'chef/nil_argument'
require 'chef/mash'
require 'chef/exceptions'
require 'chef/log'
require 'chef/config'
require 'chef/providers'
require 'chef/resources'
require 'chef/shell_out'
~~~
这部分似乎定义了Chef的版本、配置、日志、resources、providers、shell等应用层面的东西。
~~~
# 第二部分
require 'chef/daemon'
~~~
这一部分,似乎是定义了chef daemon的功能代码。我们知道,chef-client可以以daemon模式运行。
~~~
# 第三部分
require 'chef/run_status'
require 'chef/handler'
require 'chef/handler/json_file'
~~~
这一部分,似乎是定义了chef的运行状态、handler的信息, 用于处理chef的内部状态信息。
~~~
require 'chef/monkey_patches/tempfile'
require 'chef/monkey_patches/string'
require 'chef/monkey_patches/numeric'
require 'chef/monkey_patches/object'
require 'chef/monkey_patches/file'
require 'chef/monkey_patches/uri'
~~~
这一部分,我们看到了monkey_patches 以及熟悉的Ruby内部类同名的文件,几乎可以肯定,这是chef自己对于Ruby提供的类进行了monkey patch,添加了自己要用的方法。 我们在前面讲类与模块那一节,也用这个代码举过例子。
当然,以上都是目测, 让我们继续查看lib/chef下面的文件吧。
### lib/chef
我们打开lib/chef目录,发现里面很多文件和文件夹, 跟我们上面根据chef.rb的猜想有点差别。那么我们把chef.rb中require的文件一个个看一下吧,看完后发现,并没有囊括完chef目录中的所有文件。
那这些文件都是干嘛的呢?
先来剧透一下吧。
如果你看过chef-server-webui,应该可以知道,在chef-server-webui里面也用到了chef,主要是用到了chef/rest这部分文件。
还有上面说过的bin目录下的的命令,也需要用到chef/application.rb以及 chef/application/, chef/api_client/等下面的各种文件。
lib/chef下面还有一部分文件是用于测试的,那些我们就暂时不关心了。
我们继续看看chef最重要的东西。
### Knife
knife是chef中不可或缺的工具。 我们通过knife命令和chef server、node交互。那么让我们来看下knife的工作机制:
### 相关文件
knife 相关的最主要的文件是:
- lib/chef/knife.rb
- lib/chef/knife/*.rb
我们打开lib/chef/knife/目录,可以看到里面定义了很多rb文件, 这里几乎定义了全部的knife的命令实现,以及辅助方法。
如果想深入了解的话,具体看代码吧。
### Chef Client
chef client的所有行为都在lib/chef/client.rb中。
### Chef中的DSL方法
构成recipe的那些chef resource,都在chef/resource.rb和chef/resource/目录下面被定义。
尤其是在chef/resource/目录下面,定义了我们常用的resource:
- file
- template
- execute
- directory
- template
- user
等等。具体可以再深入研究源码。
bundler
最后更新于:2022-04-01 02:42:45
# bundler
bundler主要用于管理Ruby应用程序的依赖关系,并按照此依赖关系安装所需的Gems。
安装bundler:
~~~
gem install bundler
~~~
当运行bundle install命令来安装Gems时,bundler会使用当前目录下的名为Gemfile的文件来处理依赖关系。
Gemfile 是 Bundler 引入的一種檔案,可以在 Gemfile 裡面聲明會用到的 RubyGems。
### 簡單的 Gemfile
可以在隨意目錄下,使用 bundle init 來建立一個 Gemfile:
~~~
# A sample Gemfile
source "https://rubygems.org"
# gem "rails"
~~~
### Gem 来源
Gemfile 需要指定一個 Gem 來源站點,Bundler 才知道要去那裡幫你把 RubyGems 抓回來。
~~~
source "https://rubygems.org"
~~~
使用 http 也可以,https 比較安全。
可以宣告多個來源:
~~~
source "https://rubygems.org"
source "https://ruby.taobao.org"
~~~
上面的優先。
### Gem 撰寫方式
最簡單的便是:
~~~
gem "rails"
~~~
另可指定版本、require 方式、所屬的 group 等其他選項。
### 指定 RubyGems 的版本
指定版本的方法與 Rubygems 版本規範相同。舉幾個例子:
~~~
gem "nokogiri"
gem "rails", "4.1.1"
gem "rack", "~> 1.5"
gem "kaminari", "~> 0.15.1"
gem "uglifier", ">= 1.3"
gem "sinatra", "~> 1"
~~~
這裡看到 4 種版本指定方式:
1.
不指定版本
~~~
gem "nokogiri"
~~~
盡可能使用最新版。
1.
明確指定版本
~~~
gem "rails", "4.1.1"
~~~
指定使用 Rails 4.1.1。
1.
~>
~~~
gem "rack", "~> 1.5"
~~~
會使用介於 1.5 ~ 2.0 的最新版(不包含 2.0)。
~~~
gem "kaminari", "~> 0.15.1"
~~~
會使用介於 0.15.1 ~ 0.16.0 的最新版(不包含 0.16.0)
~~~
gem "sinatra", "~> 1"
~~~
會使用介於 1.0 ~ 2.0 的最新版(不包含 2.0)
1.
>=
~~~
gem "uglifier", ">= 1.3"
~~~
會使用 1.3 以上(包含 1.3)的最新版本。
### 版本指定方式總結
这些版本号都是语义化版本,可以自己搜索相关概念。
~~~
指定方式 起始 ~ 結束(不包含)
">= 3.0" 3.0 ... ∞
"~> 3.0" 3.0 ... 4.0
"~> 3.0.0" 3.0.0 ... 3.1
"~> 3.5" 3.5 ... 4.0
"~> 3.5.0" 3.5.0 ... 3.6
"~> 3" 3.0 ... 4.0
~~~
來源:[https://github.com/rubygems/rubygems/blob/master/lib/rubygems/version.rb](https://github.com/rubygems/rubygems/blob/master/lib/rubygems/version.rb)
### RubyGems require 方式
一般在有使用 Bundler 的專案裡,會有個檔案,裡面有如下程式碼(以 Rails 為例):
~~~
require 'bundler/setup'
Bundler.require(:default, Rails.env)
~~~
告訴 Bundler 自動幫你 require RubyGems。
但有些 RubyGem 的名稱不符合 Ruby 的命名,或是不想要 Bundler 自動幫我們 require。
Gem 的名稱不同會有不同的 require 語句,以及類別、模組的命名方式,以下是 Rubygem 的命名慣例:
| Gem 名稱 | require 語句 | 主要的類別與模組 |
|-----|-----|-----|
| fancy_require | require "fancy_require" | FancyRequire |
| ruby_parser | require "ruby_parser" | RubyParser |
| net-http-persistent | require "net/http/persistent" | Net::HTTP::Persistent |
| rdoc-data | require "rdoc/data" | RDoc::Data |
| autotest-growl | require "autotest/growl" | Autotest::Growl |
| net-http-digest_auth | require "net/http/digest_auth" | Net::HTTP::DigestAuth |
命名慣例有幾點要注意的是:
- 多個單字使用底線(_)區隔。
- 為某個 Gem 寫的擴充功能請用橫槓 -,比如 rspec-rails 是 rspec 對 Rails 的擴充。
- 正確混用底線與橫槓 -,如:net-http-digest_auth。
- 不要使用全大寫。OSX、Windows 不分大小寫的,很容易出錯。
看一些例子:
~~~
gem "recaptcha", :require => "recaptcha/rails"
gem "activeadmin", github: "gregbell/active_admin"
~~~
### RubyGems 分組管理
使用 :group 選項:
~~~
gem "bullet", :group => "development"
~~~
多個群組使用 :groups 選項:
~~~
gem "factory_girl_rails", :groups => [:development, :test]
~~~
### 指定 RubyGems 來源為 Git
可以指定托管在使用 Git 平台上(如 GitHub、Bitbucket、Gitcafe)的 RubyGem。
使用 :git 選項:
~~~
gem "rails", :git => "git://github.com/rails/rails.git"
~~~
### 指定分支
使用 :branch 指定。
~~~
gem "rails", :git => "git://github.com/rails/rails.git", branch: "4-1-stable"
~~~
GitHub
由於 GitHub 很流行,所以有一個特別的 :github 選項。
~~~
gem "rails", github: "rails/rails"
~~~
### 指定 RubyGems 來源為本機路徑
使用 :path 選項:
### 指定 RubyGems 的平台
根據 Ruby 版本不同,指定不同的 debugger:
~~~
gem debugger, platforms: [:ruby_18, :ruby_19]
gem byebug, platforms: [:ruby_20, :ruby_21]
~~~
### Gemfile 指定 Ruby
使用 2.1.2:
~~~
ruby "2.1.2"
~~~
使用 2.0.0-p481:
~~~
ruby "2.0.0", patchlevel: "481"
~~~
使用 JRuby:
~~~
ruby "1.8.7", engine: "jruby", engine_version: "1.6.7"
~~~
注意 Ruby 的版本要與 JRuby 的 engine_version 相容。
### berkshelf
chef的cookbook管理工具berkshelf,跟bundler的用法几乎雷同了。
Rubygems与gem
最后更新于:2022-04-01 02:42:43
# Rubygems 与 gem
### 什么是RubyGems?
RubyGems是一个方便而强大的Ruby程序包管理器,Ruby的第三方插件是用gem方式来管理,非常容易发布和共享,一个简单的命令就可以安装上第三方的扩展库。特点:能远程安装包,包之间依赖关系的管理,简单可靠的卸载,查询机制,能查询本地和远程服务器的包信息,能保持一个包的不同版本,基于Web的查看接口,能查看你安装的gem的信息。
### 安装RubyGems
官方站点 [http://rubygems.org/pages/download](http://rubygems.org/pages/download)
下载地址:[http://rubyforge.org/frs/?group_id=126](http://rubyforge.org/frs/?group_id=126)
Windows 用户直接下载.zip压缩包,解压缩,从CMD提示窗口下进入 setup.rb所在目录, 运行 ruby setup.rb 即可安装。
Ruby1.9.1 以后的版本自带RubyGems,直接在CMD窗口下输入指令 gem update --system 升级到最新版即可。
### gem包的安装方式:
RubyGems.org 是官方的Gem托管中心,RubyGems就是从这里远程下载gem包的。RubyGems 将所有的gem包 安装到 /[ruby root]/lib/ruby/gems/[ver]/ 目录下,这其中包括了cache、doc、gems、specifications 4个目录,cache下放置下载的原生gem包,gems下则放置的是解压过的gem包。当安装过程中遇到问题时,可以进入这些目录,手动删除有问题的gem包,然后重新运行 gem install [gemname] 命令即可。
RubyGems命令详解:
我们以目前最新的 rubygems 1.8.16 为例:
~~~
# 查看RubyGems软件的版本
gem -v
# 更新升级RubyGems软件自身
gem update --system
# 更新所有已安装的gem包
$ gem update
# 更新指定的gem包
# 注意:gem update [gemname]不会升级旧版本的包,可以使用 gem install [gemname] --version=[ver]代替
$ gem update [gemname]
# 安装指定gem包,程序先从本机查找gem包并安装,如果本地没有,则从远程gem安装。
gem install [gemname]
# 仅从本机安装gem包
gem install -l [gemname]
# 仅从远程安装gem包
gem install -r [gemname]
# 安装gem包,但不安装相关文档文件
gem install [gemname] --no-ri --no-rdoc
# 安装指定版本的gem包
gem install [gemname] --version=[ver]
# 删除指定的gem包,注意此命令将删除所有已安装的版本
gem uninstall [gemname]
# 删除某指定版本gem
gem uninstall [gemname] --version=[ver]
# 查看本机已安装的所有gem包
gem list
# 列出远程RubyGems.org 上有此关键字的gem包(可用正则表达式)
gem list -r keyword
# 列出远程RubyGems.org 上所有Gmes清单,并保存到文件。
gem list -r > remote_gem_list.txt
#查看所有gem包文档及资料
gem server
#显示RubyGem使用帮助
gem help
#列出RubyGem命令一些使用范例
gem help example
~~~
### gem源
默认gem源是rubygems.org,但是在国内,因为一些众所周知的原因,导致我们下载安装gem会报网络问题的错,所以,我们可以用淘宝源,ruby.taobao.org。
### require
使用require,可以加载ruby文件到你需要的文件中。require后面加载的文件名,不需要写Ruby文件的后缀.rb。
例如Chef gem中有一些代码:
~~~
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
require 'chef/application/apply'
~~~
$:.unshift语句是把和当前文件**FILE**相对的lib目录添加到PATH中, PATH是Ruby的文件查找路径数组,require命令,会在里面查找。
上面的例子表示, require lib/chef/application/apply.rb文件到当前代码文件中。
Ruby中除了require,还有load、autoload等,这个我们以后再说, 先了解require就够用了。
Chapter 4: Chef源码架构
最后更新于:2022-04-01 02:42:40
# Chapter 4: Chef源码架构
本章我们来讲一下Chef的源码架构,及其内部的工作机制, 包括chef-client/ knife/ chef-solo等命令的工作机制。
代码块
最后更新于:2022-04-01 02:42:38
# 代码块
一个块包含的代码块。你可以分配一个名称,一个块。块中的代码总是被括在大括号里({})或是do...end里。
~~~
[1, 2, 3].each do |i|
puts i
end
#=> 1
2
3
~~~
上面这个例子, each方法后面加一个do...end结构,那就是一个块。
Ruby中任何一个方法你都可以传递一个块。
~~~
def test;end
test{ puts i}
~~~
~~~
def test
yield
end
test{puts "hello test!"}
def test(x)
yield(x)
end
test('world!'){|x| puts "hello #{x}"}
~~~
yield关键字不仅可以挂载块(block)代码,而且可以给块传递参数。
~~~
def test(&block)
block.call("world")
end
test{|msg| puts "hello #{msg}"}
~~~
block到了方法内部,已经被&转化为了一个Proc对象。
~~~
def test(&block)
inner_test(&block)
end
def inner_test
yield("haha!")
end
test{|msg| puts "hello #{msg}"}
~~~
test方法传进去的block被转化为了Proc对象,而其内部的inner_test又利用「&」把这个Proc对象转化为了块(block)
### 作用域
在Ruby中,关键字class、moduel、def都有自己的作用域范围。
~~~
class People
father = 'God'
def my_father
puts father
end
end
module Faith
def my_father
father = 'My God'
puts father
end
end
~~~
然后我们创建一个对象:
~~~
person = People.new
person.my_father
#=> NameError: undefined local variable or method `father' for #<People:0x00000003248990>
~~~
我们看到,报错了,因为作用域的问题,在my_father方法中,找不到这个father的变量。
~~~
person.extend Faith
person.my_father #=> "My God"
~~~
我们把Faith模块extend到person对象之后,就可以调用my_father方法,这是因为在模块Faith中定义了father变量。两个father变量明显不同。
### 穿透作用域的块
块(block)有个功能,就是可以穿透上面所说的作用域。
~~~
class People
father = 'The God'
define_method :priest do
puts "I can talk with #{father}"
end
end
person = People.new
person.priest #=> "I can talk with The God"
~~~
上面代码中, define_method是可以动态的定义一个方法,使用define_method方法的主要原因是想使用块,因为它后面可以加一个块, 也就是 do ... end中包括的内容。我们可以看到上面块中的代码, 直接使用了Class作用域的father变量,并且成功的输出了结果。
这就证明了block有穿透作用域的能力。
### lambda 和 proc
我们在前面展示了一些block的例子。 我们说Ruby一切皆对象,但是这个block,确不是对象, 不过也不影响那句话,因为block是无法单独存在的,它必须要依靠于一个方法。如果你想让一个block单独被调用,那么就需要把块变成一个Proc对象。
~~~
lambda = ->(x, y){x * y}
#=> #<Proc:0x00000002e593c0@(pry):35 (lambda)>
lambda.call(2,3) #=> 6
#也可以省略call,但不可以省略点「.」
lambda.(2,3) #=> 6
~~~
lambda,是Proc对象的一种类型。它是一个可以被call的对象。
~~~
proc = proc{|x, y| x * y}
#=> #<Proc:0x00000002d1ee38@(pry):38>
proc.call(2, 3) #=> 6
proc.(2, 3) #=> 6
~~~
proc也是一个Proc对象, 注意看lambda和proc生成的Proc对象的差别。
具体的差别可以查看我的blog文章:[大话Rubyblock: http://tao.logdown.com/posts/166766-vernacular-ruby-block](http://tao.logdown.com/posts/166766-vernacular-ruby-block)
### 结语
在Chef中, block的应用是非常常见的, 比如我们随便写个cookbook,都必须得用到, 下面的例子来自于 [chef-server的cookbook](https://github.com/opscode-cookbooks/chef-server/blob/master/recipes/default.rb):
~~~
# Ensure :file_cache_path exists
directory Chef::Config[:file_cache_path] do
owner 'root'
group 'root'
recursive true
action :create
end
~~~
现在你看懂这样的代码了吗?
控制语句
最后更新于:2022-04-01 02:42:36
# 控制语句
每个编程语言都必须有控制语句,Ruby也不例外。Ruby中的控制语句包含下面几种:
- 条件选择语句 (if/ case)
- 循环语句 (while/ for/ until)
- 迭代语句 (each)
### 条件选择语句 (if/ case)
~~~
x=1
if x > 2
puts "x is greater than 2"
elsif x <= 2 and x!=0
puts "x is 1"
else
puts "I can't guess the number"
end
~~~
通过上面例子,基本可以了解if语句的用法了。和其他语言没什么两样。不过在Ruby中,如果你的条件只有一重,可以使用下面的格式,也叫if修饰符:
~~~
word = 'world'
puts "hello #{word}" if word
#=> "hello world"
~~~
上面的两段代码中,if后面只要跟表示true的表达式,就可以执行if分支,否则就执行其他if分支,比如elsif,或else分支。。
### 循环语句 (while/ for/ until)
直接看例子吧:
~~~
@i = 0;
num = 5;
while @i < num do
puts("Inside the loop i = #@i" );
@i +=1;
end
~~~
while循环语句,上面的代码中有一段:
~~~
puts("Inside the loop i = #@i" );
~~~
这句中的#@i 实际上是#{@i},在变量为非本地变量情况下,Ruby允许你省略{}。
~~~
$i = 0;
$num = 5;
begin
puts("Inside the loop i = #$i" );
$i +=1;
end while $i < $num
~~~
可以使用begin...end while语句,类似于其他语言的do...while。
until语句:
~~~
$i = 0;
$num = 5;
until $i > $num do
puts("Inside the loop i = #$i" );
$i +=1;
end
~~~
跟while的条件相反才执行。
for语句:
~~~
for i in 0..5
puts "Value of local variable is #{i}"
end
~~~
### 迭代语句 (each)
each是我们最常用的迭代语句,一般都用它来替代循环语句。
~~~
[1, 2, 3].each do |i|
puts i
end
~~~
这里涉及到do...end代码块,我们下节会讲到这个概念。
真与假
最后更新于:2022-04-01 02:42:33
# 真与假
任何语言中都有需要表示真假的数据类型,Ruby中是Boolean数据类型。
~~~
true.class #=> TrueClass
false.class #=> FalseClass
~~~
在Ruby中,true,表示真, false表示假。 他俩分别是TrueClass和FalseClass的对象。
但是,除了true和false, Ruby中还有nil:
~~~
nil.class #=> NilClass
~~~
nil在Ruby中代表空值,所以它也可以用来被表示假的概念:
~~~
!nil #=> true
~~~
!为一个取反操作, 上面代码,我们为nil取反,返回true。
那么让我们来看看0和1, 因为某些语言里,0代表假, 1代表真,比如C语言。
~~~
!0 #=> false
!1 #=> false
~~~
我们为0、1取反,结果都返回false。说明在Ruby中不管是0,还是1,都表示true。
### 结语
其实,在Ruby中, 除了false和nil之外,其他都为true。我们可以在下节的条件语句中感受一下。
数据类型
最后更新于:2022-04-01 02:42:31
# 数据类型
Ruby核心库(core,不包括标准库)的数据类型分为:
- 数字(Numeric -> 包含了Integer/Float子类, Integer又包含了Fixnum和Bignum子类)
- 字符串(String)
- 符号 (Symbol)
- 数组 (Array)
- 哈希 (Hash)
- 范围 (Range)
### 数字Numeric
数字,分为很多种,整数、小数。 Ruby中数字类型很简单:
~~~
1.class #=> Fixnum
1.class.superclass #=> Integer
1.class.superclass.superclass #=> Numeric
~~~
一个数字字面量,即为一个数字类型,ruby中一切皆对象,所以这个数字字面量也可以响应一个叫class的方法,来返回它自己的类别,是Fixnum。 superclass方法,则返回一个类的父类。
Ruby允许我们像上面代码那样,连续调用,「返回的值」即为下一次调用的「消息接收者」,当然你得保证返回的值是可以响应那个消息的。
还有小数:
~~~
0.1.class #=> Float
0.1.class.superclass #=> Numeric
~~~
还有比较大的整数呢。
~~~
(2**200).class #=> Bignum
(2**200).class.superclass #=> Integer
# 当值很大的时候
(2**2000000000) #=> 返回一个Infinity,代表无穷大
(2**2000000000).class #=> Float
~~~
结论:
Ruby中整数的值在一个小范围的时候,是Fixnum类型, 当数值很大的时候,则变成了Bignum类型,太大的值会返回一个固定的值Infinity,代表无穷大。而这个Infinity是一个Float对象。
Ruby中的小数, 是一个Float类型。 Float类型在Ruby中算是一个缺陷,因为它在计算过程中会产生误差,会带来一些bug。实际应用中都不会去使用这个数字类型。我们一般用Ruby标准库中的BigDecimal类型。
### BigDecimal
示例:
~~~
require 'bigdecimal'
sum = BigDecimal.new("0")
10_000.times do
sum = sum + BigDecimal.new("0.0001")
end
print sum #=> 0.1E1
~~~
因为是Ruby标准库,使用的时候一定要require那个库。
### 字符串(String)
Ruby的字符串,分为两种。一种是双引号包含的东西,一种是单引号所包含的东西。
~~~
str = " hello world "
str = ' hello world '
~~~
他们的区别,就是单引号的字符串,基本是原样输出,只能识别'\''这样形式的转义符。而双引号则完全识别转义符。
~~~
str = '\'' #=> "'"
str = "\'" #=> "'"
str = 'hello \n \t world!' #=> "hello \\n \\t world!"
str = "hello \n \t world!" #=> "hello \n \t world!"
~~~
### #{}
使用#{}可以把一个变量,「镶嵌」到一个字符串里面:
~~~
hello = "hi"
str = " #{hello} world " #=> "hi world"
str = '#{hello} world' #=> "\#{hello} world"
~~~
通过上面的代码,也可以看出来单引号字符串和双引号字符串之间的差别,就是单引号字符串,无法识别#{}这个操作符。
### %q / %Q 操作符
对于一些比较复杂的字符串,像:
~~~
"#{name} said: \"Clap your hands!\""
~~~
这种字符串里面,双引号也作为了字符串的内容, 影响了可读性。 那么Ruby就提供了一个%Q操作符来帮助我们解决这样的问题:
~~~
name = 'Alex'
%Q|#{name} says: "Try ftp://ruby-lang.org/pub/ruby/1.9/"|
%Q-#{name} says: "Clap your hands!"-
%Q/#{name} says: "Play tic-tac-toe!"/
# 省略 Q
%-#{@name} says: "Clap your hands!"-
%/#{name} says: "Play tic-tac-toe!"/
%[#{name} says: "Play tic-tac-toe!"]
~~~
%Q后面可以跟任何一对对称的符号,只要是对称就可以。
%Q代表了双引号,当%后面省略了Q,也是一样的。 而%q, 则是代表单引号.
~~~
name = 'Lee'
%q[#{name} says: "Play tic-tac-toe!"] #=> "\#{name} says: \"Play tic-tac-toe!\""
~~~
### 符号 (Symbol)
符号类型,在Ruby中用一个冒号加名字或者是字符串来表示。
~~~
:name
:_name
:"name"
~~~
你不能使用冒号和数字来声明一个符号型,否则会报错。
~~~
:1
#=> SyntaxError: unexpected tINTEGER, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END
~~~
### 符号是什么
在Ruby中,符号表示一个「名字」。它有点类似于字符串,但和字符串又有不同。在Ruby中,一切皆对象,符号类型,也是对象,每个对象都有一个唯一的对象标识符object_id, 对象标识符一样,就代表是同一个对象,否则就是不同的对象。我们来比较下符号和字符串。
~~~
:name.object_id #=> 66088
:name.object_id #=> 66088
:name.object_id #=> 66088
"name".object_id #=> 10285860
"name".object_id #=> 10058700
"name".object_id #=> 8904020
~~~
从上面的代码里可以看出, 连续三个:name,都是指同一个Symbol对象,而连续三个"name",则是三个不同的对象。
所以,Symbol对象是一种具有唯一性的类「字符串」, 因为Symbol也拥有字符串的一些行为。比如:
~~~
:name.upcase #=> :NAME
"name".upcase #=> 'Name'
~~~
当然,不是全部的字符串方法它都可以响应,你可以去ruby-doc.org去比较他们的方法。
Symbol对象,从创建开始,一直到程序退出运行,都是被存放到一个叫做符号表的地方, Ruby的垃圾回收(GC)不会清理这些符号对象。而字符串不一样, GC会清理掉那些无用的字符串对象。
在认识到符号的这些特性之后, 应该不难理解我们把符号类型用来表示一个「名字」的概念。
举个例子:
有三个双胞胎字符串对象:'name', 'name', 'name', 我们可以用符号类型来形容他们的长相, 都是:name。
### 数组 (Array)
Ruby中的数组,是一个任何对象的有序的、用整数来索引的集合。
~~~
arr = [1, 2, :a, 'name', nil]
arr = Array[1, 2, 3, :name]
arr.class #=> Array
arr[0] #=> 1
arr[1] #=> 2
#...
~~~
Ruby的数组,可以存放任何对象。
~~~
arr = [1, 2, 3, [4, 5, 6]]
arr[0] #=> 1
arr[3][0] #=> 4
~~~
上面,我们定义了一个二维数组,通过[]方法,传入索引参数,可以取得数组的值。
数组的概念很容易理解。可以结合ruby-doc.org来查看并且练习数组中内建的很多方法。
### 哈希 (Hash)
Ruby中的哈希,是一个键值对的集合。在Ruby1.8中是无序的,但是在Ruby1.9开始,哈希变成有序的了。
~~~
h = {a: 100, b: 200}
h = Hash["a" => 1000, "b" => 2000]
h[:a] #=> 100
h['a'] #=> 1000
~~~
Hash是在{}中被包含的字面量,也可以通过Hash[]方法来创建一个hash,参考上面的代码。我们可以通过一个key来获取其所对应的值。
Hash的key,必须是唯一的。
~~~
h = {a: 1, b: 2, a: 3}
h[:a] #=> 3
~~~
key必须是唯一,所以上面的代码中,h[:a]取出的值是3.
我们提倡使用符号类型来作为Hash的key,这也是Ruby1.9开始引入下面的hash结构的原因之一:
~~~
h = {a: 1, b: 2}
#等同于
h = {:a => 1, :b => 2}
~~~
但也不是所有的情况都试用于上面第一种写法,比如你必须要以数字作为key,就只能用第二种形式。但是大部分情况,第一种写法已经够用了。 使用字符类型做为key,可以节省内存。因为上面我们讲过,字符串和符号的区别,就是每个字符串都是不同的对象,而Ruby中的对象是占用内存的,所以我们尽量让其少生成点对象。这也是Ruby鼓励我们在Hash中使用字符类型作为key的原因。
同样,Hash有很多内建方法,可以去ruby-doc.org中自行查看并且练习。
### 范围 (Range)
范围(Range)发生无处不在:一月至十二月,0到9,50至67行,依此类推。
(1..3) 和 (1...3) 都表示一个范围。
~~~
(1..3).class #=> Range
(1...3).class #=> Range
~~~
范围有一个起点,一个终点,产生连续的序列值。如果是两个点把起点和终点相连,则范围的连续值包含终点值。如果是三个点,则范围的连续值不包含终点值。
可以参考我们学过的数学中的开闭区间来理解这个概念。
~~~
(1..3).each{|i| puts i}
#=> 1
2
3
(1...3).each{|i| puts i}
#=> 1
2
~~~
我们使用each方法来迭代范围中的值,可以看得出两个点和三个点范围类型的区别。
### 结语
Ruby中的主要数据类型大概就讲这么多,Ruby还有其他的数据类型,比如时间日期, 表示真假的Boolean型,甚至标准库中所包含的数据类型,这些大家可以参考ruby-doc.org去自行学习。
类与模块
最后更新于:2022-04-01 02:42:29
# 类(Class)与模块(Module)
### 类
我们在对象和方法那一节中,提过这个概念。 比如 「人类」。
在面向对象概念中,一个类代表一组对象共同特征的抽象集合。比如「人类」,代表了人的共同特征,可以直立行走、会说话、会思考等人类特征。
~~~
*注:最好你可以打开irb或者是pry跟着练习*
在命令行输入 irb 或 pry,回车,你就进入到了一个互动的Ruby Shell界面里,你可以在里面输入代码,并且会马上得到运行结果。
chef-shell命令,就是基于irb来做的。
~~~
**用Ruby代码表示就是:**
~~~
class People
def walk
puts 'can'
end
def say
puts 'hello word'
end
def think
puts 'I got!'
end
end
~~~
Ruby中用class关键字来声明一个类,注意类名People,是大写字母开头。没错,这个People,就是一个常量。
~~~
person = People.new
~~~
我们可以使用new方法来创建一个对象, 这里的person,就是我们创造的一个人。
~~~
person.walk
person.say
person.think
~~~
人类的行为, 这个person都可以做。
当你运行以上代码之后,会发现,这些方法都返回nil,这是因为Ruby的方法,如果你没有明确使用return,默认只返回方法内部最后一行的运行结果,上面的方法中,最后一行都是puts语句,puts语句会返回nil。
当然你可以使用return语句来给方法指定返回值。比如:
~~~
def walk
return 'can'
end
~~~
但是他的名字呢,性别呢,种族或国籍等其他属性呢?这些个体的属性,是不可能每个人都一样的,那么我们该怎么设定呢?
~~~
class People
def initialize(name="", gender="")
@name = name
@gender = gender
end
end
~~~
我们打开类Peopole,使用initialize 方法,来给一个类添加属性。就是当你使用new方法创建一个对象的时候,这个对象可以被赋予属性。
上面的代码里, @name和@gender,都是实例变量, 而参数name,gender都是本地变量,也就是局部变量,它们可以被赋予默认值,但是,他们只能在initialize这个方法的作用域范围内有效,所以,叫本地变量。
这样,我们就可以重新创建一个person,指定name和gender了。
~~~
person = People.new('alex', 'man')
#=> #<People:0x007fb961313ce8 @gender="man", @name="alex">
~~~
当然,你仅仅创建了@name和@gender这俩实例变量,这还不够,你还不能给这俩实例变量赋值以及获取它们的值。如下:
~~~
person.name
#=> NoMethodError: undefined method `name' for #<People:0x007fb961313ce8 @name="alex", @gender="man">
~~~
所以,你必须要实现一对set/get方法。
我们再一次打开类,添加下面代码:
~~~
class People
def name
@name
end
def name=(name)
@name = name
end
def gender
@gender
end
def gender=(gender)
@gender = gender
end
end
~~~
然后我们马上再次执行下面代码:
~~~
person.name
#=> "alex"
~~~
返回了我们期望的结果。我们也可以给person改名:
~~~
person.name = 'lee'
person.walk
person.say
person.think
~~~
### 开放类
如果你跟着上面的代码走下来,会发现, 我们两次都是直接打开People这个类来修改,每次修改都没有重新添加之前的代码,而People这个类生成的对象的行为,却是只增不减。 尤其是你从其他语言转过来的话,比如Java,会感到奇怪。
没错,这正是Ruby的特性之一: Open Class,开放类。
Ruby的类是可以随便打开的,非常自由。
自由所带来的结果是,有可能会被滥用,比如,你可以打开Ruby内置的类来添加方法:
~~~
class String
def to_iii
self.to_i
end
end
"111".to_iii
#=> 111
~~~
这种方式,有可能会影响到String类内置的方法,因为你无法记住每一个内置的方法,假如你添加的方法和内置的方法重名的时候,就完了,Ruby是不会警告你的,这样可能会引起非常严重的bug。
我们把这种方式叫做monkey patch。 意思就是这种方式,比较原始,就像猴子没进化到人这么高级一样。 不过Ruby2.1给出了一个方案,具体可以参考我的blog的相关文章:[「Ruby2.1 Refinements」告别Monkey Patches](http://tao.logdown.com/posts/171266-ruby21-refinements-farewell-to-monkey-patches),这里不再累述。
### Chef的monkey patchs
在Chef这个工具里,也使用了这种方式:
链接:[https://github.com/opscode/chef/blob/master/lib/chef.rb](https://github.com/opscode/chef/blob/master/lib/chef.rb)
可以看到:
~~~
require 'chef/monkey_patches/tempfile'
require 'chef/monkey_patches/string'
require 'chef/monkey_patches/numeric'
require 'chef/monkey_patches/object'
require 'chef/monkey_patches/file'
require 'chef/monkey_patches/uri'
~~~
先不用管require这个方法,这是Chef对于Ruby原生的类,添加了自己的方法。被添加的这个方法,应该是想要被所有的相关对象都可以响应。比如,我们打开「chef/monkey_patches/object」
~~~
class Object
unless new.respond_to?(:tap)
def tap
yield self
return self
end
end
end
~~~
可以看到, Chef这种添加方式还是比较安全的。它加了一层保护,「unless new.respond_to?(:tap)」, 只有Object的对象不能响应这个tap方法,它定义的这个tap方法才可被响应。
这样起到一个很好的保护作用。
### 模块
模块,使用module关键字来定义的,你可以把它当做一组方法集合。比如有一群人,他们有共同的兴趣,比如踢球、看球、打dota。 但是这些兴趣并不是所有人类都有的,这个时候就需要模块了。
~~~
module Interest
def kickball
puts "i like kicking ball"
end
def read
puts "i like reading books"
end
def dota
puts "i like playing dota"
end
end
~~~
然后我们就可以为某个人赋予这些兴趣:
~~~
person = People.new('张三', 'man')
person.extend Interest
person.kickball #=> "i like kicking ball"
person.read #=> "i like reading books"
person.dota #=> "i like playing dota"
~~~
注意,这里person是一个对象,我们可以使用extend来把Interest模块的方法来拓展给person,让他也拥有这些兴趣。这种方式,叫做Mixin。
模块,也可以作为命名空间来使用:
~~~
class Chef
module Mixin
module Command
# ...
end
module Template
# ...
end
end
end
~~~
这样,我们可以使用Chef::Mixin::Command和Chef::Mixin::Template来使用这两模块,这就起到一个命名空间的作用。
### 继承
我们可以使用模块来为类添加一组方法, 当然也可以使用继承来添加一个子类。 那我们刚才的例子来说,有一组相同兴趣的人群, 他们首先是人类,然后才是有这种兴趣的人。
~~~
class Fan < People
def kickball
puts "i like kicking ball"
end
def read
puts "i like reading books"
end
def dota
puts "i like playing dota"
end
end
~~~
我们定义一个类Fan, 使用 < 关键字来让这个类继承自People,那么Fan就拥有了People的所有属性和行为。
~~~
person = Fan.new('李四', 'man')
person.kickball #=> "i like kicking ball"
~~~
同样,李四和前面的张三,拥有了相同的对象。
我们可以把继承和模块结合起来使用, 因为兴趣分很多类型,有运动、阅读、打游戏,而运动种类,阅读的种类和游戏的种类也很多,结合起来使用,会让我们的代码更加灵活:
~~~
module SportInterest
def walk; 'walk' end
def run; 'run' end
def swimming; 'swimming' end
# ...
end
module ReadInterest
def read_book; 'read book' end
def watching_tv; 'watching tv' end
# ...
end
module GameInterest
def cs; 'play cs' end
def dota; 'play dota' end
def wow; 'play wow' end
# ...
end
class SportFan < People
include SportInterest
end
class ReadFan < People
include ReadInterest
end
class GameFan < People
include GameInterest
end
~~~
这样,我们可以随便为各个兴趣模块里面添加各种项目,也不会影响到相关的Fan类。
再加上命名空间的概念,重构下上面代码,就是下面这样:
~~~
module Interest
module Sport
def walk; 'walk' end
def run; 'run' end
def swimming; 'swimming' end
# ...
end
module Read
def read_book; 'read book' end
def watching_tv; 'watching tv' end
# ...
end
module Game
def cs; 'play cs' end
def dota; 'play dota' end
def wow; 'play wow' end
# ...
end
end
class SportFan < People
include Interest::Sport
end
class ReadFan < People
include Interest::Read
end
class GameFan < People
include Interest::Game
end
~~~
这样,代码是不是看着更清晰了。
标识符
最后更新于:2022-04-01 02:42:27
# 标识符
Ruby的标识符用于命名局部变量、实例变量、类变量、全局变量、常量、方法以及块。标识符的命名有一套固有规则。
### 命名规则
- 必须以字母、数字或下划线为开头,长度只受硬件内存大小限制
- Ruby的保留字不得用作标识符的命名。
### Ruby中标识符的分类
-
变量
- 本地变量, 也叫局部变量,比如 「name」。
- 实例变量,以@为开头的变量,为实例对象所用,比如「@name」。
- 类实例变量, 也是以@为开头的变量,为类所用,比如「@name」。
- 类变量, 以@@为开头的变量,也是为类所用,比如「@@name」。
- 全局变量,全局范围内都可以被使用的变量,类似于类变量,但是写法不同,比如「$name」
- 常量, 以大写字母为开头,比如「Klass,Node」。Ruby中常量的值是可以被修改的,只是出一个警告。
-
方法与块
方法和块的命名规则,和本地变量一样,如果是两个单词以上,用下划线分割。比如「get_name」
### Chef中变量的命名
贴一段Chef的源码,大家来感受一下:
~~~
class Chef
class Node
extend Forwardable
def_delegators :attributes, :keys, :each_key, :each_value, :key?, :has_key?
attr_accessor :recipe_list, :run_state, :override_runlist
# RunContext will set itself as run_context via this setter when
# initialized. This is needed so DSL::IncludeAttribute (in particular,
# #include_recipe) can access the run_context to determine if an attributes
# file has been seen yet.
#--
# TODO: This is a pretty ugly way to solve that problem.
attr_accessor :run_context
include Chef::Mixin::FromFile
include Chef::DSL::IncludeAttribute
include Chef::DSL::PlatformIntrospection
include Chef::Mixin::ParamsValidate
# Create a new Chef::Node object.
def initialize
@name = nil
@chef_environment = '_default'
@primary_runlist = Chef::RunList.new
@override_runlist = Chef::RunList.new
@attributes = Chef::Node::Attribute.new({}, {}, {}, {})
@run_state = {}
end
# ...
end
~~~
对象与方法
最后更新于:2022-04-01 02:42:24
# 对象与方法
先教大家一句咒语: **Ruby中,一切皆对象。**
### 对象(Object)
Ruby是一门解释执行的动态语言,同时,它也是一门完完全全的面向对象语言。
对象,很好理解,它表示客观世界中某个具体的个体事物,它是面向对象世界中的基本单元。它是某一个类的具体实例, 一个对象,应该包含其唯一的标识符,属性(attributes)以及方法(Methods)。
属性(attributes)即为一个对象的特点,用于描述这个对象的个性,而方法(Methods),则为这个对象能够提供的服务,及其行为。
举个例子:
~~~
人类,是一个类别。 人类中某个个体,为张三。 他是男性,中国人,黄色人种。他会做饭,是大厨,他唱歌也很好听。
~~~
那么用Ruby代码来描述的话,应该是这样的:
~~~
# 人类
Class People
attr_accessor :name, :gender, :race, :nationality
def initialize(name='', gender='', race='', nationality='')
@name = name
@gender = gender
@race = race
@nationality = nationality
end
end
# 上帝造了张三
person = People.new
person.name = "张三"
person.gender = '男'
person.race = 'yellow'
person.nationality = 'China'
def person.chef
puts "good"
end
def person.sing
puts "good"
end
~~~
上面的代码, Class即为创建一个类,比如People, 是指人类。
attr_accessor , 声明了People类的属性,比如,人类是有名字(name)、有性别(gender)、有种族(race)、有国籍(nationality)。
我们可以使用new方法,来使用People类来造一个对象,也就是一个人, 我们用person表示。
~~~
person = People.new
~~~
我们使用「点」来指定person的name,并赋与其值。
~~~
person.name = '张三'
~~~
而实际上,attr_accessor,是动态生成了类似下面的方法:
~~~
class People
# ...
def name
@name
end
def name=(name)
@name=name
end
# 其他属性方法类似
end
~~~
def,是声明一个方法, 为对象创建一个行为。 比如上面的person.name = '张三', 就是允许给person起名字。
而像下面这样的代码:
~~~
def person.chef
puts "good"
end
~~~
当执行person.chef的时候,会输出‘good’。
则是给「张三」这个个体创建一个头衔,厨师(chef),代表他会做饭。这个会做饭的技能, 只是他这一个个体特有的,你可以再创建一个人「李四」来尝试一下:
~~~
other_person = People.new
other_person.name = '李四'
~~~
你可以让「李四」来调用chef方法, 则会报错。
### 消息传递
我们上面的代码中: person.chef , 我们叫它方法调用。
而Ruby更愿意把它称作,消息传递。
person,是一个对象, 是一个消息接收者。
chef,是一个消息。
通过一个「点」, 我们把chef这个消息传递给person, 假如person可以响应这个消息,那么它就会响应结果,否则,则报错无法响应。
消息传递,是对象之间传递信息的唯一手段。
还记得我们的咒语吗? Ruby中一切皆对象。我们使用Ruby,就是使用对象, 想让对象为你工作,那么你就得给他传递消息。
包括要设置对象自己的属性,也是使用消息传递的方式,这也是上面代码中为什么要有「attr_accessor」方法。
**另一句咒语: Ruby中一切都是消息传递(「方法调用」)**
所以,Ruby里各种内建数据结构,比如字符串、数组、Hash等,也内置了一些方法,你也可以用同样的方式来调用。可以去[ruby-doc.org](http://ruby-doc.org)来查找这些方法。
### Chef中的抽象
回头看我们的自动化管理工具Chef中的一些概念,我们更容易理解这种对象与消息的思想。
比如,在OpsCode给出的apache Cookbook中,设置attributes的一段代码:
~~~
node.default["apache"]["dir"] = "/etc/apache2"
node.default["apache"]["listen_ports"] = [ "80","443" ]
~~~
node, 即为一个对象,Chef中把具体要配置管理的服务器,抽象为一个类Node,而上面代码中的node,即为那个具体的要使用Chef进行配置管理的服务器或者是其他Chef可配置管理的任何东西。
### 思考:
结合本节的内容,来思考下面这段代码的意思?
~~~
node.default['apache']['dir'] = '/etc/apache2'
~~~