$> gem install sinatra
require 'sinatra' get '/' do "Just Do It" end
注意:如果你的Ruby版本低于1.9,需要在代码第一行添加:"require 'rubygems'"。
这就是一个基础的Sinatra应用:在文件头部,我们导入了Sinatra的Gem包。从第二行开始,是一个action。在Sinatra中,这叫做handler(处理器),因为它负责处理路由(route)和动作(action)。第二行最开始的部分(get)表明我们将使用哪一种HTTP method,在例子中,我们用的是Http GET,因为我们在视图“获得”某个页面。接下来的部分是有关路由的字符描述,即"/",这是本应用的根URL。代码块(code block)表明当用户访问这个URL时会发生什么。在这个简单的例子中,只是返回了一行文字“Just Do It”,这行文字最终会在浏览器渲染成页面。通常来说,handler代码块的最后一行总会在浏览器里渲染什么。
我们需要启动Sinatra服务器来看看这个例子是不是正常工作。打开终端,在main.rb所在的路径执行:
$> ruby main.rb
几秒钟后,你会看到这样的信息:
== Sinatra/1.2.6 has taken the stage on 4567 for development with backup from Thin
这时服务器已启动。在浏览器输入http://localhost:4567,页面就会显示一行“Just Do It”。至此,我们已创建了一个Sinatra应用。很简单吧?
内嵌模板
我们的Just Do It引用除了显示一行字符外没做其他事情。接下来我们会进一步扩展这个应用,这里我们先创建一些模板文件,他们包含HTML和用Ruby生成的动态内容。Slim是一个神奇的模板引擎,因此这部分工作变得很简单。在继续下面的内容之前,我们首先安装Slim的Gem包。
$> gem install slim
现在将main.rb的内容修改成如下内容:
require 'sinatra' require 'slim' get '/' do slim :index end __END__ @@layout doctype html html head meta charset="utf-8" title Just Do It link rel="stylesheet" media="screen, projection" href="/styles.css" /[if lt IE 9] script src="http://html5shiv.googlecode.com/svn/trunk/html5.js" body h1 Just Do It == yield @@index h2 My Tasks ul.tasks li Get Milk
doctype html html head meta charset="utf-8" title Just Do It link rel="stylesheet" media="screen, projection" href="/styles.css" /[if lt IE 9] script src="http://html5shiv.googlecode.com/svn/trunk/html5.js" body h1 Just Do It == yield
同样保存以下内容到index.slim
h2 My Tasks ul.tasks li Get Milk现在最好重启服务器,然后看看我们的视图是不是还能正确被渲染。动态内容
下面我们进一步来看看 Sinatra的一些特性。让我们创建一个新的handler用来接收一些动态的输入(main.rb):get '/:task' do @task = params[:task] slim :task end
你也许会注意到route项中包含了字符串“:task”,这是个命名参数,用冒号开头加以识别。命名参数从 URL 中获得值,可利用“params”哈希进行存取。在代码块的第一行,有一个名为‘@task’ 实例变量,它的值与“params[:task]”相等,内容就是写在 URL 斜杠后的一些东西。实例变量相当有用,因为它们可以在视图中被引用。新的路由中指定了一个“task”视图,我们需要先创建它,将之以“task.slim”文件名保存到views文件夹中,内容如下:h2 My Tasks = @task
这里用了“=”号来获得Ruby变量的值。Slim 会输出等号之后的 Ruby 运算结果。在例子中,则是 @task 实例变量的值,它与URL相匹配。重启服务器,然后在浏览器里查看 ‘http://localhost:4567/get-milk’,结果如图:现在看起来不错,不过还有改进的空间,我们在 hanlder 里加了点新的代码美化一下页面:get '/:task' do @task = params[:task].split('-').join(' ').capitalize slim :task end
现在当你浏览 “http://localhost:4567/get-milk”,界面看起来是这样的:表单 在结束这部分教程之前,我们来看一下如何通过表单来提交任务。打开 index.slim 文件,将其内容替换成
form action="/" method="POST" input type="text" name="task" input.button type="submit" value="New Task >>"我们还需要一个 handler来处理提交上来的表单。如果你看了上面的代码,“action” 属性说明表单本身会被提交到URL ‘/’,而 “method” 属性该表单会使用POST方法。这直接引出 Sinatra 的第二个杀手级特性:如何在 handler 中指定请求方法。把下面的代码添加到 main.rb 中:
post '/' do
@task = params[:task] slim :task end这段代码和我们之前用到的列出任务的 handler 非常相像,不同是现在我们通过表单的输入来获得任务。这个新的 handler 定义为POST路由,这意味这它只能响应HTTP POST请求。因此,我们可以为同一个路由“/” 定义两个 handler,并根据不同的请求类型赋予不同的代码段。
表单被提交的时候,它会将 params[:task] 的值设置为我们在页面输入的东西。用相同的办法,可以通过引用 params 哈希存取任意值。
你可以试着在http://localhost:4567/创建一些新的任务,不过现在我们还不能创建任务清单,也没有办法删除修改它们。我们需要某种方式来保存这些任务,这些内容我们会在本教程的第二部分中详述。
在本系列教程的第一部分,我们演示如何配置Sinatra并显示了几个页面。现在是真正有趣的部分,在本节中,我们将演示如何用数据库来保存任务。这里我们会使用SQLite作为本地数据库,正如它的名字说表述的,这是一个轻量级的文件数据库,无需任何配置。如果你没有安装过这个数据库,可以参考本页的一些简单介绍。
为了和数据库交互,我们会使用DataMapper。这个ORM的工作方式和 Active Record 类似,但在方法和语法上略有不同。
为了配合SQLite使用DataMapper,下面的 Gem包需要被安装:
$> gem install data_mapper dm-sqlite-adapter sqlite3
我们需在 main.rb 文件的头部导入DataMapper:
在本教程的第二部分,我们利用DataMapper将任务保存到后台数据库,同时我们还用Sinatra创建了Web前端以便显示、添加、删除和结束任务。现在这个 Just do it 应用功能齐备,但是样子还有点丑陋。require 'sinatra' require 'data_mapper'
要使用数据库,我们用了一行代码来设置其连接,告诉DataMapper连接到名为 “development.db”的SQLite数据库,该数据库文件将被保存在应用所在目录中。DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite3://#{Dir.pwd}/development.db")
注意:这里的DATABASE_URL是留给后面介绍部署到生产环境时使用PostGres时的URL。因为Heroku不支持SQLite数据库。因此这里的DATABASE_URL是Heroku部署时添加Postgres后,执行heroku addons:add heroku-postgresql 后返回的URL数据库连接URL。Task模型
为了将任务项保存到数据库中,我们需要创建一个task类。下面紧跟在 main.rb 的数据库连接代码之后:class Task include DataMapper::Resource property :id, Serial property :name, String, :required => true property :completed_at, DateTime end DataMapper.finalize
“include DataMapper::Resource” 这行代码将 DataMapper 的 Resource 模块 mixin 到 Task 类中,使它与 DataMapper 链接在一起。其他Ruby类也可以通过同样的方法成为 DataMapper 的资源。这行代码之后,是 Task 类的属性。:id 属性带有 Serial 类型赋予了每个任务一个自动增长的标识符。之后是任务实际需要的两个属性,即任务的名称(该项带有一个required属性)和完成时间。
添加任务
现在我们在 IRB 中来创建一些任务。打开一个命令行窗口,并执行 irb 命令( Ruby 安装路径需在PATH中)。
在开始添加任务之前,我们通过模型迁移(migratie)来创建 Tasks 表。使用下面的命令来完成:$> irb ruby-1.9.2-p180 :002 > require './main' DataObjects::URI.new with arguments is deprecated, use a Hash of URI components (/home/daz/.rvm/gems/ruby-1.9.2-p180/gems/dm-do-adapter-1.1.0/lib/dm-do-adapter/adapter.rb:231:in `new') => trueruby-1.9.2-p180 :003 > Task.auto_migrate! => true
接下来添加一些任务。你可以在 irb 使用 create 方法来完成。如例子所示:
ruby-1.9.2-p180 :004 > Task.create(name: "get the milk") => # ruby-1.9.2-p180 :005 > Task.create(name: "order books") => # ruby-1.9.2-p180 :006 > Task.create(name: "pick up dry cleaning") => # ruby-1.9.2-p180 :007 > Task.create(name: "phone plumber") => #
这些任务会被保存到数据库中。想要查看这些任务,我们需要简单修改一下 main.rb 处理根 URL 的 handler:
get '/' do @tasks = Task.all slim :index end
Task.all 从数据库中获取所有的任务,并将它们以数组的形式保存在实例变量 @tasks 中。 如前所述,实例变量对所有的视图是可见的,因此我们可以在 index.slim 中遍历数组中的每个任务并以列表显示出来:
form action="/" method="POST" input type="text" name="task[name]" input.button type="submit" value="New Task >>" h2 My Tasks ul.tasks - @tasks.each do |task| li.task= task.name
在命令行中输入 ruby main.rb 来启动服务器,刷新页面你就可以看到一个相当不错的任务列表。现在我们还需要从页面来添加任务,这并不难,因此我们已经有了一个类似的表单,只需要将它与 DataMapper 连在一起,这样任务就会被保存到数据库中去。
在上一节中,当表单被提交时,我们用下面的 handler 用来处理 POST 请求:
post '/' do @task = params[:task] slim :task end
只需要如下做稍微的修改:
post '/' do Task.create params[:task] redirect to('/') end
随着表单提交的信息被保存在 params 哈希表用于创建一个新的任务。(在本例子中,只有任务的名字)然后页面被重定向到首页 (这时候会使用 GET 请求),所以会显示任务列表其中也包括我们最新添加的任务。在浏览器中尝试一下吧。
删除任务
到目前为止一切顺利,我们能为列表添加任务,现在的问题是如何删除它们呢?为此我们需要为每个任务添加一个删除的按钮,通过该按钮我们可以发送一个删除的请求到服务器上。在这儿,我打算将查看任务的视图代码分离到它自己的文件中。将下面的代码保存到views文件夹,文件名是 “task.slim”。li.task id=task.id = task.name form.delete action="/task/#{task.id}" method="POST" input type="hidden" name="_method" value="DELETE" input type="submit" value="×" title="Delete Task"
现在将 index.slim 改为:
form action="/" method="POST" input type="text" name="task[name]" input.button type="submit" value="New Task >>" h2 My Tasks ul.tasks - @tasks.each do |task| == slim :task, locals: { task: task }
注意:首先,在 task 视图,我们需要为删除按钮创建一个表单。为此我们包含了一个隐藏的值域,带有属性 name=”_method” value=”DELETE”。这是因为大部分浏览器不理解 HTTP 的 PUT 和 DELETE 动词,而了解 GET 和 POST。为了解决这个问题, Sinatra 使用隐藏的表单值域来“欺骗”浏览器以正确处理请求。这意味着当删除的表单被提交的时候,它能象DELETE请求一样被处理。
在 index 视图中,注意任务视图被以下面的形式被引用:
== slim :task, locals: { task: task }
这里演示了 Sinatra 如何处理局部模板(partial) – 你只需要嵌入调用一个模板即可。这例子中,我只需说明 task.slim 文件中使用的 task 变量与 block中传递的 task 变量的引用是相同的即可。
剩下来的哦就是写一个 handler 来删除任务。你可能注意到 action 属性里的 URL 形式是 /task/#{task.id}。对应的 handler 是这样的:
delete '/task/:id' do Task.get(params[:id]).destroy redirect to('/') end
这段代码简单地通过 URL 中的 id 查找被点击的任务,然后用 destroy 方法将它从数据库中删除。之后页面被重定向到根页面以列出剩下的任务。
结束任务
我们还需要能够结束任务。在我们创建 Task 类的时候,我们指定了一个 completed_at 属性来说明某项任务是什么时候完成的。如果这个属性值为空,我们可以假定该任务尚未被完成。打开 taks.slim,并做如下修改:li.task = task.name form.update action="/task/#{task.id}" method="POST" input type="hidden" name="_method" value="PUT" -if task.completed_at.nil? input type="submit" value=" " title="Complete Task" -else input type="submit" value="?" title="Uncomplete Task" form.delete action="/task/#{task.id}" method="POST" input type="hidden" name="_method" value="DELETE" input type="submit" value="×" title="Delete Task"
在最后一个表单里包含了一个隐藏的表单域说明这是一个PUT 请求,这是因为我们正在修改任务类的属性(事实上,这里应该用 PATCH 请求,不过 Sinatra 1.3 尚未支持)。我们还根据任务是否结束显示了不同的按钮。我们还为任务的状态添加了一些视觉效果,比如结束的任务显示的是一个打勾的符号,而未结束的任务则显示的是空白。
注意:action 属性里给定的 URL 是完全相同的,这是因为 Sinatra 可以根据不同的 HTTP 动词来采取不同的动作。我们现在添加一个新的 handler来处理这个请求,在 main.rb 的尾部添加这些代码:
put '/task/:id' do task = Task.get params[:id] task.completed_at = task.completed_at.nil? ? Time.now : nil task.save redirect to('/') end
这里根据 URL里的 id来查找任务,如果 completed_at 属性尚未被设置,则将其设置为为当前时间,如果它已经被设置,则再次将其置空。换句话说,这个按钮充当了切换任务结束或者未结束状态的开关
重新启动服务器,尝试一下添加、删除、结束任务。用了还不到40行代码,我们就有了一个功能齐备的线上待办事项应用。不过现在还不是尽善尽美,我们在文章的第三部分再加以改进。在本章中,我们继续对这个应用做些美化修饰,并增加一些功能,让你能够创建多个任务列表。
添加样式
现在我们用样式表来对应用进行美化修饰。在你的 “layout.slim” 中添加下面这行代码:link rel="stylesheet" media="screen, projection" href="/styles.css"
现在创建一个名为” styles.css”的文件,并保存在”public”文件夹中,将下面 CSS 内容添加到该文件中:
.completed{ text-decoration: line-through; } .tasks{ padding:0; list-style:none; width:400px; } .task{ position:relative; padding:2px 0 2px 28px; border-bottom: dotted 1px #ccc; } form.update{ position:absolute; bottom:2px; left:0; } form.update input{ background:white; color:white; padding:0 2px; border:solid 1px gray; cursor:pointer; } .tasks li.completed form.update input{ color:#47FD6B; } form.delete{ display:inline; } form.delete input{ background:none; cursor:pointer; border:none; }
刷新页面,现在页面是不是看起来好多了,也更像一个真正的任务列表了。你也许注意到,该样式表中有个 ‘completed’ 类。 到目前为止,我们显示已完成的任务的方法是为它添加完成的时间,这样看起来不怎么样。我们来修改一下 task 视图,添加 ‘completed’ 类。这样利用样式表,已完成的任务看起来就是完全不同的风格。打开 “task.slim” 做如下修改:
li.task id=task.id class=(task.completed_at.nil? ? "" : "completed") = task.name form.update action="/task/#{task.id}" method="POST" input type="hidden" name="_method" value="PUT" -if task.completed_at.nil? input type="submit" value=" " title="Complete Task" -else input type="submit" value="?" title="Uncomplete Task" form.delete action="/task/#{task.id}" method="POST" input type="hidden" name="_method" value="DELETE" input type="submit" value="×" title="Delete Task"
关键在于头一行,该行使用了三元操作符来检查任务是否完成,并为完成的任务添加了 “completed” 类。 这个CSS会划掉已经完成的任务。如果再次启动应用,你就会觉得这样看起来更好也更自然 – 将任务标记成完成时还有某种视觉的效果。
任务列表
接下来我们让应用具备添加多个任务列表的功能。为此我们会创建一个 List 类, List 类里包含着许多任务。 DataMapper利用关联在 List 类和 Task 类之间创建关系。我们在每个类的尾部添加一行代码。Task 模型使用 belongs_to 声明,而 List 模型则使用 has n 声明。在后台,这些声明会为 Task 类添加一个 list_id 属性,用来跟踪这个任务是属于哪个列表,但是我们不需要直接访问这个属性。每个类都会获得一些额外的方法,因此你可以通过 List.tasks 来获得某个列表的任务,或者通过 Task.list 来获得任务所在的列表。打开 main.rb,新增一个 List 类,对并 Task 类做如下修改:class Task include DataMapper::Resource property :id, Serial property :name, String, :required => true property :completed_at, DateTime belongs_to :list end class List include DataMapper::Resource property :id, Serial property :name, String, :required => true has n, :tasks, :constraint => :destroy end DataMapper.finalize
由于我们创建新的模型,我们需要更新底层的数据库,这可以通过 DataMapper 的 auto_migrate! 方法来完成。在控制台窗口中打开 irb :
$> irb require './main' DataMapper.auto_migrate!
注意:这个操作会清空数据库中原有的所有任务。
增加删除列表
接下来我们创建一些处理器来处理任务列表。这些处理器与文章第二部分的 task 处理器类似,这里我们只列出全部代码,请将它们保存在 main.rb 的后面:post '/new/list' do List.create params['list'] redirect to('/') end delete '/list/:id' do List.get(params[:id]).destroy redirect to('/') end
这些代码相当直观:一个处理器基于表单提交的参数创建新的任务清单,另一个依据 URL 中的 id 删除任务清单。接下来我们对 index 页面的表单做点小修改,使它可以用于创建清单而不是任务。
form.new action="/new/list" method="POST" input type="text" name="list[name]" input type="submit" value="+ List" ul.lists - @lists.each do |list| == slim :list, locals: { list: list }
这个视图包含了一个实例变量 @lists,它代表了数据库中的所有的列表,该变量仍未被赋值,所以我们需要更新相关的处理器,在 main.rb 中找到和根 URL 对应的处理器,如下:
get '/' do @tasks = Task.all slim :index end
将它改为:
get '/' do @lists = List.all(:order => [:name]) slim :index end
这段代会查找所有的列表而不再是所有的任务。 We will get the tasks from the database on a list by list basis.
Index 视图的最后一段指向了一个名为 list 的视图,我们我们就来创建它。该视图用于显示每个列表中的任务。创建一个新的文本文件,并将下面的内容保存在 view 文件夹下的 list.slim 文件中。
li.list h2= list.name form.new action="/#{list.id}" method="POST" input type="text" name="task[name]" ul.tasks - list.tasks.each do |task| == slim :task, locals: { task: task } form.destroy action="/list/#{list.id}" method="POST" input type="hidden" name="_method" value="DELETE" input type="submit" value="×"
这段代码和 index 视图中的原有代码十分相近。开头部分列出了列表的名字,接下来是一个表单,用来添加任务。紧随其后的是任务清单,使用了和本文第二部分的 task 视图一样的代码。代码的最后一段是用来访问 delete 处理的表单,这样列表就可以被删除掉了。
将任务添加到列表中
我们现在还要做点修改以确保任务被正确地添加。任务属于某个列表,因此需要确保指定了任务要添加到哪一个列表中。通过在创建任务的 URL 中指定了列表的 id ,我们做到了这一点。注意 list.slim 中的这一行:form.new action="/#{list.id}" method="POST"
改行指定了处理表单 post 的 action 必须包含该表单所在的列表的 id .现在我们用下面的处理器来添加任务:
post '/' do Task.create params['task'] redirect to('/') end
需要做如下变更:
post '/:id' do List.get(params[:id]).tasks.create params['task'] redirect to('/') end
该处理器获取 URL 中指定的列表的 id ,并在数据库中找到该列表,之后用表单中的参数创建了属于该列表的新任务。
更多的样式
在本文将要结束之际,我们要做的就是为列表再添加一些样式。打开 style.css ,添加下面这几行:.lists{ padding:0; list-style:none; overflow:hidden; } .list{ float: left; width:23%; margin:0 1%; border-top:solid 5px #ccc; }
你还需要确保下面这行已经删除:
width:400px;
这就是我们所做的!现在你已经有了一个功能齐整的 To Do list 应用。在本系列文章的最后一部份,我们将用 SASS 添加更多的样式表,并用 Heroku 将它部署到 Web 上。这是本系列文章的最后一部分。本教程的目标是带领从未用过Sinatra的用户从头开始创建一个应用,并将之最后发布。
到前三部分结束时,我们已经完成了 To Do List 应用的大部分功能。在本系列的最后一部分,我们将先看看如何用SaSS来编写CSS ,之后将本应用发布到 Heroku 云服务上。
Logo
每个上点档次的应用都应有一个 logo,所以我创建了一个简单的“tick” logo,你可以在GitHub上找到。我们用它来作为本应用的头条的背景图片。打开 Styles.css 并添加下列CSS:h1.logo{ font-family: helvetica,arial,sans-serif; font-size: 24px; color: white; padding: 64px 0 0; margin: 0 auto; text-transform: uppercase; text-align: center; font-weight: normal; letter-spacing: 0.3em; background: transparent url(/logo.png) 50% 0 no-repeat; }(注意 – 也许你还发现,你需要为 layout.slim 的 h1 元素添加一个 ‘logo’ 类,这段 CSS 才会起作用。)
我还用这个 logo 创建了个网站小图标(favicon),你也可以在 Github 上找到。为了显示这个网站小图标,你需要在 html 中添加一个链接,所以 layout.slim 最后看起来是这样的:
doctype html html head meta charset="utf-8" title Just Do It! link rel="shortcut icon" href="/favicon.ico" link rel="stylesheet" media="screen, projection" href="/styles.css" /[if lt IE 9] script src="http://html5shiv.googlecode.com/svn/trunk/html5.js" body h1.logo Just Do It! == yield
注:本文是翻译自 http://rubysource.com/just-do-it-learn-sinatra-i 的系列文章。我按照英文学习了Sinatra,并部署到Heroku上。这是我部署后的站点地址:http://planws.herokuapp.com/Getting Sassy
Sass (Syntactically Awesome Stylesheets) 是一种 CSS 模板语言,你可以用 Sass 来编写样式表,该样式表会在服务器端被编译成 CSS 并发送到浏览器。Sass 有2个变种,原有的 “Indented Sass”和后来的“SCSS”(Sassy CSS)。这儿我打算用 SCSS,因为它看起来更象普通的 CSS 。不过你还应该两种都尝试下看那种更适合你。要把 Sass 集成到 Sinatra 项目中,我们需要安装 Sass gem:
$> gem install sass
接下来,我们在 main.rb 的尾部添加下列处理器:
get '/styles.css' do scss :styles end
现在我们拿掉原有的布局文件 Styles.css 的引用,取而代之的是名为“styles.scss”的 Sass 文件。目前这个文件还不存在,我们马上会创建它。这个文件应保存在 “views” 文件夹。将 styles.css (在 public 文件夹中)的内容拷贝到 styles.scss 中,然后删除 styles.css。
如果你在浏览器里看一下我们的应用,并没有什么变化。这是因为 SCSS 是 CSS 的一个超集,因此任何正确的 CSS 都能正常工作,我们上面把标准 CSS 的内容搬到 SCSS 中,但现在我们将要大幅修改我们的样式表了。
CSS 变量
人们最希望 CSS 有的功能之一是变量。 无需等待,Sass 让我们可以马上使用变量。例如我们可以用变量表示颜色,并在标记中引用。$orange:#D78123; $blue:#1E1641; $green:#02d330; $grey: #999;
现在我们可以在样式表的其余部分引用这些颜色。例如将每个清单的头部改为桔色,Styles.scss 可以这样写:
.list h1{ color: $orange; }
这大大提高了可维护性,如果很多元素都是用该颜色,我们需要颜色加深时,我们只需要在一处修改就可以了。有了变量,你可以做的事情还很多,包括数学运算,你可以参考有关文档。
CSS Mixins
Mixins 是 Sass 相当酷的功能,让你可以创建模块化和可重用的 CSS 。例如 我们创建了一个名为 button 的 mixin ,它包含了有关的 CSS 可以将标准链接变为按钮。@mixin button($color){ background:$color; border: 2px solid darken($color,15%); cursor: pointer; color: #fff; width: 150px; margin-left:4px; }
接下来我用 @include 语句将这些通用样式添加到样式表的其他 CSS 声明中:
.button{ @include button($orange); }
这会把 button mixin 的所有 CSS 添加到具有”button”类的所有元素中。当然我们可以在 “button” 类中直接编写 CSS,但是 mixin 允许我们将其添加到任何其他的 CSS 声明中而无需任何重复代码。注意我们的 mixin 带有一个 color 参数,允许我们无需重复任何代码就能创建不同颜色的按钮。同时我们还使用前面创建的变量 $orange 来创建桔色按钮。
嵌套 CSS
Sass 另一个最棒的功能是能到嵌套你的声明。这可以大大减少你打字的时间,并为你的 CSS 文件呈现更强的结构化。例如,下面 CSS 被用来样式化每个清单的列表和标题:
.list{ float: left; background: #fff; } .list h1{ color:$orange; }
通过在 .list 声明嵌套 h1 声明里,可以将上面的代码写地更有效率,如下:
.list{ float: left; background: #fff; h1{ color:$orange; } }
编译之后,上面的 Scss 得到和上面一样的 CSS,但它采用了嵌套结构,显示上更为简洁。如果你用更深层次的嵌套 CSS,还可以节约很多打字的时间。
Sass 的功能如此强大,现在编写 CSS 变得简单多了。在 Sinatra 中采用 Sass 也如此简单,没有什么理由不在所有的项目中使用它。特别是你可以只先标准的 CSS,然后在需要的时候,在加入额外的“时髦”的功能。上面举的例子只是初步展示了 Sass 的一些粗浅的功能。读者可以通过阅读文档、更重要地编写自己的代码来感受 Sass。我相信一旦你开始用 Sass ,你就不会再用回普通的 CSS!
我们无法在这里列出完整的 Scss 文件内容,你可以在 Github 得到完整代码。现在美化应用的阶段大功告成,是时候发布应用了。
Heroku
Heroku 提供了特别棒的服务,能让用户在云端托管 Sintra 应用。它对于基本的站点是免费的,包括数据库存储,当你的应用变得流行时,还可以进行扩容。它消除了托管网站的所有麻烦,可以用在大型的可伸缩的实际运作的网站上。Heroku 还提供了优秀的文档与支持。首先你要做的是,就是获得一个 Herkou 帐号,如果你还没有的话。接下来,确认你是否在系统里安装了Git。如果还没有的话,Github 上提供了很有用的指导。请特别注意有关设置 SSH 密匙的章节,你需用它来访问 Heroku。
下一步,你将要安装 Heroku gem ,用来和你发布在 Heroku 上的应用交互:
$> gem install heroku
如果你是首次访问 Heroku, 你需要添加你刚创建 SSH 密钥(只需要做一次):
$> heroku keys:add
现在我们在 Heroku 上创建应用:
$> heroku create justdoit
(注意,你可能需要为你的应用取一个不同的名字,因为 justdoit 已经被我占用了!)
现在你配置好了 Heroku ,可以开始发布应用。
Bundler
在把应用发布到 Heroku 上之前,我们需要要用 Bundler 来管理应用的所有依赖。这包括我们所使用的所有 gems ,还有这些 gems 依赖的 gems。 Bundler 将所有这些 gems 直接安装到应用的目录。这意味着你的应用现在“捆绑”了所有的依赖,而无需依赖于服务器是否默认安装所需的东西。(Heroku 默认根本不提供任何 gem)。你还可以管理 gem 的版本号因此你可以确保上线时使用的版本号和开发时一样。为此我们需要安装 Bundler gem:
$> gem install bundler
现在我们要在应用的目录中创建两个新的文件。最重要的一个,我们需要一个 Gemfile 列出所有使用的 gem 的清单。将下面的代码保存在应用的根目录下,名为 Gemfile(无扩展名):
source :rubygems gem "sinatra" gem "datamapper" gem "slim" gem "sass" gem "dm-postgres-adapter", :group => :production gem "dm-sqlite-adapter", :group => :development
最后两行指定了数据库适配器,Datamapper 用之连接数据库。目前在开发机上我们使用的是 Sqlite,但 Heroku 使用 PsotgreSQL。如何根据环境的不同而使用不同的数据库,这可以通过在 Bundler group 配置不同的数据库适配器 gem 来实现。
我们还需要 Rackup 文件。打开你的文本编辑器,将下面代码保存为 config.ru:
require 'bundler' Bundler.require require './main' run Sinatra::Application
最后你要做的是在本地测试所做的修改,我们运行 bundle install 命令,它会创建 .bundle 目录,里面包含所有在 Gemfile 中列出的 gem。
bundle install --without production
–without production 标志确保任何在production group 里的 gem 不会被安装。
你无需产品环境中运行这个命令,Heroku 检测到 Gemfile 就会自动运行该命令。不过我们必须告诉 Heroku 避免使用任何“开发”组里的 gem,通过下面简单的命令即可完成:
heroku config:add BUNDLE_WITHOUT="development:test" --app justdoit
(注意 你应该是使用你应用自己的名字而不是 justdoit)
在我们开始发布应用之前,我们应该在本地机器上检查一切运行正常。这需要通过另一种方式来完成,现在我们该用上 rackup 文件了,打开命令行窗口,输入下面命令:
$> rackup
现在在端口 9292 上会启动服务器。访问 http://localhost:9292/ 就可以看到它。
发布
现在剩下的就是把应用发布到 Heroku。首先我们需要创建一个名为 .gitignore 的文件,将之保存在根目录,内容是以下2行:development.db .bundle
这可以防止任何不必要的文件被添加到 git 仓库中。我们不想 Sqlite 数据库文件被放到 git 仓库因为 Heroku 不需要它。我们也不想 .bundle 目录被发送到生产服务器上(它有自己的版本).
接下来的工作是初始化 Git 仓库:
git init git add . git commit -m 'initial deployment'
最后我们把应用推送到 Heroku.
git push heroku master
一旦你开发你自己的应用,非常容易更新 Heroku 上的代码。用下面的命令,很简单就能将代码提交到 Git 仓库然后将其推送到 Heroku上:
git add . git commit -m 'some message about the updates' git push heroku master
注意:在推送之前,检查main.rb中数据库setup部分的代码,将DATABASE_URL改为你addon的数据库调用URL。下面是我的数据库连接代码:
DataMapper.setup(:default, ENV['HEROKU_POSTGRESQL_IVORY_URL'] || "sqlite3://#{Dir.pwd}/development.db")
最后要做的就是通过运行迁移在服务器上配置数据库。 Hereko 命令行界面允许你访问 irb 控制台以便和你的应用交互:
$> heroku console Ruby console for justdoit.heroku.com >> require './main' >> DataMapper.auto_migrate!
你可以从命令行打开站点:
heroku open
这会带你跳转到你的线上应用。你可以看到我最后完成的 Just Do It ! 请随意体验一下。我已经把所有的源代码放到 Github 上,你可以查看所有代码的全貌。
是时候结束本系列文章了。
我们来为这系列文章写一个结论。我希望它能给你足够的信息让你开始使用 Sinatra 并渴望了解更多。学习本教程的最好方法是自己动手 Sinatra 入门的起点是如此的低,因此开始创建一个新的应用并把弄代码是如此的简单。