Updated at: June 23, 2017
软件框架(Software framework)是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。
非常多的Web框架都实践一个叫做MVC的软件架构设计模式,将软件分成三个部分:
ORM(Object-relational mapping)可以用物件导向语法来操作关联式数据库,容易使用、撰码十分有效率,不需要撰写繁琐的SQL语法,也增加了程式码维护性。
# SQL
SELECT * FROM orders, users WHERE orders.user_id = users.id AND orders.status = "Paid" LIMIT 5 ORDER BY orders.created_at;
# Rails
Order.where(:status => "paid").includes(:user).limit(5).order("created_at")
指定任意URL对应到任一个Controller的动作,跟档案位置是无关的
附带了非常多开发Web会用到的函式库,例如Template、Email、Session、快取、JavaScript/Ajax、测试等
==================================================
撰写出重复的程式码是件坏事
Rails会默认各种好的设定跟惯例,而不是要求你设定每一个细节到设定档中。
使用Resources和标准的HTTP verbs(动词)来组织你的应用程式是最快的方式(我们会在路径一章详细介绍这个强大的设计)
CakePHP、Grails、TurboGears、Pylons、web2py、catalyst …
市面上流行成熟的CMS系统多为PHP写成,例如Drupal、WordPress等。当然也有用Ruby写的,例如Radiant。如果这些架站系统刚好符合你的需求,那就不一定需要Rails。
在硬件资源有限的行动装置及嵌入式系统上,仍是静态语言的天下
Ruby也是目前做Domain-specific language(DSL),特别是Internal DSL最为成功的程式语言。透过DSL,程式不但可以拥有非常好的可读性,也可以大幅增加生产力。成功的DSL函式库例如有:Rake建构工具、RSpec测试工具、Chef服务器设定工具、Cucumber验收测试等。
JRuby与Ruby最大的差异在于一些需要编译的RubyGem套件:有些因为效能要求而用C语言撰写的RubyGem在JRuby上不一定能够安装使用。
Rails支援的数据库包括SQLite3、MySQL、Postgres、IBM DB2、Oracle和SQL Server等。除了安装数据库软件,我们也需要安装搭配的Ruby函式库(称作Adapter或Driver)
# .gemrc
gem: --no-ri --no-rdoc
or
gem install rails --no-ri --no-rdoc
--skip-test-unit
Gemfile | 设定Rails应用程式会使用哪些Gems套件 |
---|---|
README | … |
Rakefile | 用来加载可以被命令行执行的一些Rake任务 |
app/ | 放Controllers、Models和Views |
config/ | 应用程式设定、路由规则、数据库设定等等 |
config.ru | 用来启动Rack服务器的设定 |
db/ | … |
doc/ | 你的文件 |
lib/ | 放一些自定的Module和类别档案 |
log/ | … |
public/ | 唯一可以在网络上看到的目录, 是你的图档、JavaScript、CSS和其他静态档案摆放的地方 |
bin/ | 放rails这个指令和放其他的script指令 |
test/ | 单元测试、fixtures及整合测试等 |
tmp/ | … |
vendor/ | 第三方(gem..)外挂的目录 |
Ubuntu上默认没有任何JavaScript直译器可以给Rails使用。你可以装Node.js或是安装therubyracer这个Ruby套件来获得JavaScript直译器。
get "welcome/say_hello" => "welcome#say"
get "welcome" => "welcome#index"
root :to => "welcome#index"
YAML是一种可读性高,用来表达设定资料的资料格式。它严格要求缩排(建议为两个空白),且冒号后面必须有一个空格。一般我们会预期YAML的值解析出来是字串,因此如果内容是数字或多行文字时,建议加上引号以避免字串解析错误。例如 password: “123456”。如果没有加上引号,这串数字会被解析成Fixnum物件而不是字串String,后续可能造成型别判断错误。
请注意Model的名称是用单数person,而Controller照RESTful惯例是用复数people
单独用ruby -w去执行发生错误的程式,例如ruby -w app/controller/welcome_controller,这会打开Ruby的警告模式来获得更准确的语法错误讯息。
==================================================
ActiveRecord的资料验证(Validation)功能,可以帮助我们检查资料的正确性, errors.full_messages
外卡路由 match ‘:controller(/:action(/:id(.:format)))’, :via => :all
<p><%= link_to 'Back to index', :controller => 'events', :action => 'index' %></p>
<%= form_for @event, :url => { :controller => 'events', :action => 'update', :id => @event } do |f| %>
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
<%= f.label :description, "Description" %>
<%= f.text_area :description %>
<%= f.submit "Update" %>
<% end %>
def update
@event = Event.find(params[:id])
@event.update(event_params)
redirect_to :action => :show, :id => @event
end
ActiveModel::ForbiddenAttributesError in EventsController#create的错误讯息,这是因为Rails会检查使用者传进来的参数必须经过一个过滤的安全步骤,这个机制叫做Strong Parameters
def create
@event = Event.new(event_params)
@event.save
redirect_to :action => :index
end
private
def event_params
params.require(:event).permit(:name, :description)
end
透过require和permit将params这个Hash过滤出params[:event][:name]和params[:event][:description]
Layout可以用来包裹样板,让不同样板共享相同的HTML开头和结尾部分。当Rails要显示一个样板给浏览器时,它会将样板的HTML放到Layout的HTML之中
# app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= @page_title || "Event application" %></title>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
如果你需要建立任意字段的HTML表单,而不绑在某一个Model上,你可以使用form_tag函式
利用局部样板(Partial)机制,我们可以将重复的样板独立出一个单独的档案,来让其他样板共享引用
# _form.html.erb
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
<%= f.label :description, "Description" %>
<%= f.text_area :description %>
# new.html.erb
<%= form_for @event, :url => { :controller => 'events', :action => 'create' } do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= f.submit "Create" %>
<% end %>
透过before_action,我们可以将Controller中重复的程式独立出来
讯息会被储存在Rails的特殊flash变量中,好让讯息可以被带到另一个 action,它提供使用者一些有用的资讯
kaminari这个分页套件:
def index
@events = Event.page(params[:page]).per(5)
end
<%= paginate @events %>
==================================================
什么是REST呢?表象化状态转变Representational State Transfer,简称REST,是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。相较于SOAP、XML-RPC更为简洁容易使用,也是众多网络服务中最为普遍的API格式,像是Amazon、Yahoo!、Google等提供的API服务均有REST接口
resources :events
get '/events' => "events#index", :as => "events"
post '/events' => "events#create", :as => "events"
get '/events/:id' => "events#show", :as => "event"
patch '/events/:id' => "events#update", :as => "event"
put '/events/:id' => "events#update", :as => "event"
delete '/events/:id' => "events#destroy", :as => "event"
get '/events/new' => "events#new", :as => "new_event"
get '/events/:id/edit' => "events#edit", :as => "edit_event"
Helper | GET | POST | PATCH/PUT | DELETE | |
---|---|---|---|---|---|
event_path(@event) | /events/1 show | /events/1 update | /events/1 destroy | ||
events_path | /events index | /events create | |||
edit_event_path(@event) | /events/1/edit edit | ||||
new_event_path | /events/new new |
respond_to可以让我们在同一个Action中,支援不同的资料格式,例如XML、JSON、Atom等
Atom是一种基于XML的供稿格式,被设计为RSS的替代品,广泛应用于Blog feed
def index
@events = Event.page(params[:page]).per(5)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @events.to_xml }
format.json { render :json => @events.to_json }
format.atom { @feed_title = "My event list" } # index.atom.builder
end
end
# http://localhost:3000/events.xml、http://localhost:3000/events.json、http://localhost:3000/events.atom
产生JSON还有其他方式,除了呼叫to_json或手动转Hash物件来自订格式之外,Rails有内建JSON专用的template引擎叫做jbuilder,你可以产生一个app/views/events/show.json.jbuilder的档案来产生JSON。如果需求是要制作给第三方或手机的Web APIs,那么我们就会改用jbuilder样板的方式,这样比较好写和好维护
如果想要加上这些格式的超连结,可以在URL Helper中传入:format参数
<%= link_to " (XML)", event_path(event, :format => :xml) %>
<%= link_to " (JSON)", event_path(event, :format => :json) %>
行数统计: rake stats
如何除错? 如果是Model中的程式,你可以在命令列下输入rails console,然后在Console中呼叫看看Model的方法看看正确与否。而除错Controller和Views一个简单的方法是你可以使用debug这个Helper方法,例如在app/views/events/show.html.erb中插入:
<%= debug(@event) %>
这样就会输出@event这个值的详细内容。不过,更为常见的是使用Logger来记录资讯到log/development.log里 在Rails环境中,你可以直接使用logger或是Rails.logger来拿到这个Logger物件,它有几个方法可以呼叫:
例如,你想要观察程式中变量@event的值,你可以插入以下程式到要观察的程式段落之中:
Rails.logger.debug("event: #{@event.inspect}")
在Production环境中,log/production.log会逐渐长大,可以使用 logrotate 定期整理 Rails Log 档案
==================================================
任何抽象机制都有漏洞,而唯一能完美处理漏洞的方法,就是只去弄懂该抽象原理以及所隐藏的东西
Primary Key这个字段在Rails中,照惯例叫做id,型别是整数且递增。而Foreign Key字段照惯例会叫做{model_name}_id,型别是整数。
class Event < ActiveRecord::Base
has_many :attendees # 复数
#...
end
class Attendee < ActiveRecord::Base
belongs_to :event # 单数
end
FK => belongs_to model
e.attendees.destroy_all # 一笔一笔删除 e 的 attendee,并触发 attendee 的 destroy 回呼
e.attendees.delete_all # 一次砍掉 e 的所有 attendees,不会触发个别 attendee 的 destroy 回呼
多对多
class Event < ActiveRecord::Base
has_many :event_groupships
has_many :groups, :through => :event_groupships
end
class EventGroupship < ActiveRecord::Base
belongs_to :event
belongs_to :group
end
class Group < ActiveRecord::Base
has_many :event_groupships
has_many :events, :through => :event_groupships
end
条件/依赖
has_many :attendees, ->{ order("id DESC") }
has_many :attendees, ->{ where(["created_at > ?", Time.now - 7.day]).order("id DESC") }
has_one :location, :dependent => :destroy
==================================================
resources :events do
resources :attendees, :controller => 'event_attendees'
end
# _path or _url
event_attendees_path ( @event )
edit_event_attendees_path ( @event, attendee )
new_event_attendees_path ( @event, attendee )
event_attendee_path ( @event, attendee )
<%= f.collection_select(:category_id, Category.all, :id, :name) %>
or
<%= f.select :category_id, Category.all.map{ |c| [c.name, c.id] } %>
delegate :name, :to => :category, :prefix => true, :allow_nil => true
不过 @event.category 可能是 nil,这会导致 nil.name 发生错误。一个简单的方式是改使用 @event.category.try(:name),另一招则是在 Event model 加入如上,就会有 @event.category_name 可以使用,而且允许 @event.category 是 nil
因为一个Event只有一个Location,所以我们使用了单数Resource:
resources :events do
resource :location, :controller => 'event_locations'
end
注意到我们的Controller档名还是复数,使用RESTful路由的Controller,无论在config/routes.rb中使用单数resource或复数resources形式,档名一律都是复数
用 Nested Model 顺带编辑跟新增
由于Location和Event是一对一关系,可以说Location是Event的附属资料。因此我们也可以将Location的表单直接做在Event的表单里,这样Location甚至不需要自己的Controller了: 编辑app/models/event.rb加上:
accepts_nested_attributes_for :location, :allow_destroy => true, :reject_if => :all_blank
accepts_nested_attributes_for这个方法可以让更新event资料时,也可以直接更新location的关联资料。也就是说,我们可以完全不需要修改events_controller的新增和编辑Action,就可以透过本来的params[:event]参数来新增或修改location了。这里有两个特别的参数,:allow_destroy是说我们可以在表单中多放一个_destroy核选块来表示删除,而:reject_if表示说在什么条件下,就当做没有要真的动作,例如:all_blank就表示如果资料都是空的,就不建立location资料(当然也就不会检查location的验证了)。这是因为虽然要显示location表单,但是不表示使用者一定要输入。有输入就表示必须通过Location Model的资料验证。 编辑app/views/events/_form.html.erb加上Location的表单,这里使用了fields_for来达成嵌套表单:
<%= f.fields_for :location do |location_form| %>
<p>
<%= location_form.label :name, "Location Name" %>
<%= location_form.text_field :name %>
<% unless location_form.object.new_record? %>
<%= location_form.label :_destroy, 'Remove:' %>
<%= location_form.check_box :_destroy %>
<% end %>
</p>
<% end %>
编辑app/helpers/events_helper.rb新增一个Helper:
def setup_event(event)
event.build_location unless event.location
event
end
我们会用setup_event(@event)来置换form_for中的@event,这是因为如果@event.location是nil的话,Location表单就完全不会显示,所以假如没有,就需要预先build_location给它。
# 编辑app/views/events/new.html.erb:
<%= form_for setup_event(@event), :url => events_path do |f| %>
# 编辑app/views/events/edit.html.erb:
<%= form_for setup_event(@event), :url => event_path(@event), :method => :put do |f| %>
#最后记得修改EventsController的event_params好可以接收到location参数
def event_params
params.require(:event).permit(:name, :description, :category_id, :location_attributes => [:id, :name, :_destroy] )
end