参考

最后更新于: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' ~~~
';