Sinatra à la MVC 的基础知识——配置、路由和表单
Sinatra 是一种领域特定语言 (DSL),它能让您轻松地在自己的 Web 服务器上启动并运行应用程序,该服务器可以响应 HTTP 请求并处理 URI 路由。它实际上是基于Rack构建的,Rack 是一个用于 Ruby 应用程序的 Web 服务器接口。与 Rack 相比,Sinatra 更易于使用。
这篇博客文章介绍了使用 MVC 模型为 Sinatra 设置基本配置,并以一个简单的食谱应用程序为例,讲解了如何开始使用基本路由。
文件结构和初始设置/配置
以下是使用模型-视图-控制器(MVC)架构组织应用程序的基本方法之一。值得注意的是,所有主要应用程序文件都组织在一个应用程序文件夹内,该文件夹包含与 MVC 各个组件相对应的子文件夹:/models用于存放对象模型文件、/viewsHTML 模板文件和/controllers控制器文件。
CSS样式表和JavaScript文件通常放在public文件夹下的各自子文件夹中。spec文件夹用于存放规范测试(如果有的话)。
├── Gemfile
├── README.md
├── app
│ ├── controllers
│ │ └── application_controller.rb
│ ├── models
│ │ └── model.rb
│ └── views
│ └── index.erb
├── config
│ └── environment.rb
├── config.ru
├── public
│ └── stylesheets
│ └── javascript
└── spec
├── controllers
├── features
├── models
└── spec_helper.rb
*请注意,对于较小的应用程序,有时只需要一个应用程序控制器,可以直接将其放在根文件夹中。模型和视图文件夹也可以直接放在根文件夹中,而无需放在应用程序文件夹中。
Gemfile
首先,你需要安装 Sinatra gem,我建议同时安装 Shotgun gem,这样在测试/开发过程中,每次修改代码后就无需重启 Sinatra 服务器。请确保你的 Gemfile 文件包含以下内容:
# Gemfile
source "https://rubygems.org"
gem 'sinatra'
gem 'shotgun'
Gemfile 准备就绪后,请确保bundle install在命令行中运行。
Config.ru
基于 Sinatra 和 Rack 的应用程序需要一个 config.ru加载应用程序环境和其他要求的程序,通过 ` use and` 关键字指定应该使用哪些控制器,并在 调用run 时启动应用程序服务器 。run
在下面的简单示例中,该 application_controller.rb 文件是我们唯一的控制器,环境通过该文件加载config/environment.rb 。
# config.ru
require_relative './config/environment'
run ApplicationController
config/environment.rb
这个文件将我们的应用程序代码与相应的 gem 连接起来。它加载 Bundler(从而加载 Gemfile 中的所有 gem)以及应用程序目录中的所有文件(模型、视图和控制器)。
ENV['SINATRA_ENV'] ||= "development"
ENV['RACK_ENV'] ||= "development"
require 'bundler/setup'
Bundler.require(:default, ENV['SINATRA_ENV'])
require_all 'app'
(要使用方便的 require_all 关键字,请将 gem 添加到 Gemfile 中。可以在GitHub'require_all' 上查看。)
应用控制器
好了,我们终于来看一下Sinatra的基本控制器。控制器的作用是处理所有传入的请求、响应和路由。
在application_controller.rb文件中,我们创建一个继承自Sinatra::Base.的应用程序类Sinatra::Base,这将使我们的应用程序具有 Rack 兼容的接口,可以通过 Sinatra 框架使用。
# application_controller.rb
class ApplicationController < Sinatra::Base
# code for the controller here...
end
为了让控制器能够识别上述文件结构,需要添加一些配置代码,告诉 Sinatra 在哪里可以找到该/views文件夹(Sinatra 默认在根目录查找),并/public使用一个configure代码块指定该文件夹。配置代码完成后,我们就可以编写路由了(下面我会介绍一些基础知识)。
# application_controller.rb
class ApplicationController < Sinatra::Base
configure do
set :views, "app/views"
set :public_dir, "public"
end
get '/' do
"Hello World!"
end
# and more routes...
end
对于小型应用程序来说,一个控制器就足够了,我们可以在这里定义所有 URL 以及它们如何响应“get”和“post”等请求。
请注意,应用程序控制器类可以随意命名;只需确保通过run配置config.ru文件将其挂载为正确的名称即可。“挂载”只是告诉 Rack 应用程序的哪个部分定义了处理 Web 请求的控制器。
路线
基本路线
路由的作用是将浏览器请求连接到应用程序中(控制器中)能够处理请求并发送响应的特定方法。例如,简单的路由可能只是显示或渲染一个基本的 HTML 视图。或者,另一个路由可能接收通过表单提交的数据,例如菜谱标题、配料和步骤,处理这些数据,然后显示处理后的结果——一个新的菜谱,它也是另一个 HTML 视图。
一些基本的 GET 请求可能如下所示:
# application_controller.rb
get '/' do
erb :index
end
get '/about' do
erb :about
end
`<domain>`get '/' do和 ` get '/about' do<domain>` 行对应于浏览器中的 URL。因此,如果域名是 `<domain>` tastybites.com,get '/' do则 `<domain>` 指的是该根域名。`<domain>`get '/about' do指的是 `<domain> tastybites.com/about`。
`getView` 和 `showView` 这erb :index几erb :about行代码告诉控制器要获取并显示视图文件夹中的哪个视图文件(在本例中是一个嵌入式 Ruby 文件)。因此,我们需要在视图文件夹中index.erb同时存在 `getView` 和 `showView` 文件about.erb才能使其正常工作。
如您所见,视图文件在路由中用同名符号表示。Sinatra 假定所有视图模板都直接位于该/views文件夹下,因此,如果视图恰好嵌套在某个文件夹(/views例如 `<view_name>`)中,/views/recipes/index我们需要这样引用它:`<view_name>`erb :'recipes/index'或 `<view_name> erb 'recipes/index'.to_sym`。例如:
# application_controller.rb
get '/recipes/index' do
erb :'recipes/show'
end
# Or the other way:
get '/recipes/index' do
erb 'recipes/show'.to_sym
end
动态路线
动态路由可以根据 URL 中的属性处理 HTTP 请求。这些属性由直接编码在路由中的符号表示,其值可以通过自动生成的params哈希值轻松访问,因此可用于查找或处理数据。
我们来看一个例子。假设在我们的食谱网站上,tastybites.com您希望能够通过 URL 中的 id 获取单个食谱(例如tastybites.com/recipes/27,其中 27 代表 id)。显然,我们不想为每个食谱及其 id 都编写一个路由。因此,我们使用一个符号:get '/recipes/:id'`do`,其中 `id`可以是任何数字。然后,可以通过 `do` 访问`id`:id的值。让我们看看如何使用它来获取正确的食谱::idparams[:id]
# application_controller.rb
get '/recipes/:id' do
# The :id is passed through the URL,
# which is accessible in the params hash.
# Use it to assign a recipe to an instance variable
@recipe = Recipe.find(params[:id])
erb :'recipes/show'
end
注意我们是如何利用参数哈希从数据库中查找食谱的。然后,我们将该食谱赋值给一个实例变量。Sinatra 中的实例变量非常特别,因为我们可以用它们将数据传递给视图!这就引出了下一节……
通过实例变量将数据传递给视图模板
在控制器路由中创建实例变量时,该变量可在相应的视图文件中使用。请注意,实例变量在控制器中的其他路由中不可用;它们仅在单个路由中指定的视图中可用。
让我们回到上面的例子:
# controllers/application_controller.rb
get '/recipes/:id' do
@recipe = Recipe.find(params[:id])
erb :'recipes/show'
end
假设您的文件夹中还有一个配方模型app/models,其内容可能如下所示:
# models/recipe.rb
class Recipe
attr_accessor :title, :description, :ingredients, :method
# The rest of your Recipe class code...
end
@recipe食谱有一些属性,例如标题、描述等,因此一旦在控制器路由中分配了食谱对象,我们就可以将所有这些属性直接编织到我们的 ERB 模板中:
# views/recipes/show.erb
<h1><%= @recipe.title %></h1>
<p><%= @recipe.description %></p>
<h2>Ingredients</h2>
<ul>
<% @recipe.ingredients.each do |ingredient| %>
<li><%= ingredient %></li>
<% end %>
</ul>
<h2>How to Cook</h2>
<p><%= @recipe.method %></p>
我们甚至可以在视图中遍历数据。例如,假设我们想在菜谱索引页面显示所有菜谱。首先,我们可以将所有菜谱赋值给路由中的一个实例变量recipes/index:
# controllers/application_controller.rb
get '/recipes/' do
@recipes = Recipe.all
erb :'recipes/index'
end
然后,遍历recipes/index.erb模板中的配方:
# views/recipes/index.erb
<h1>All Recipes</h1>
<% @recipes.each.do |recipe| %>
<h2>
<a href="../recipes/<%= recipe.id %>"><%= recipe.title %></a>
</h2>
<% end %>
注意我们还使用了菜谱 ID 链接到菜谱页面,该 ID 将由get '/recipes/:id'路由进行处理。很棒吧?
请注意,这些实例变量不必是模型中的对象;它们可以是您想要在视图中分配和使用的任何变量。
现在我们知道了如何从 URL 获取和使用数据,接下来让我们看看如何处理通过 POST 方法从表单发送的数据。
通过表单传递数据,并通过“post”路由捕获数据
从表单接收用户输入数据是构建 Web 应用的关键。我们只需要将表单正确地连接到控制器即可。让我们继续之前的食谱示例——这次我们要创建一个食谱,所以首先需要一个基本的表单以及用于渲染该食谱的路由。
设置路由非常简单,只需将新菜谱帖子的 URL 连接到相应的视图(该视图将包含我们的表单)即可。在本例中,假设我们希望 URL 为tastybitees.com/recipes/new:
# app/controllers/application_controller.rb
get '/recipes/new' do
erb :'recipes/new'
end
接下来,在views/recipes/new.erb文件中,我们将设置一个基本表单:
# views/recipes/new.erb
<form method='POST' action='/recipes'>
<label for="title">Title</label>
<input type="text" name="title">
<label for="description">Description</label>
<textarea name="description"></textarea>
<label for="ingredients">Ingredients</label>
<textarea name="ingredients"></textarea>
<label for="method">How to Cook</label>
<textarea name="method"></textarea>
<input type="submit" value="Submit">
</form>
这<form method='POST' action='/recipes'>行代码对于设置路由非常重要。该action属性告诉控制器应该由代码的哪一部分(即哪个路由)来处理表单。可以把它想象成一个地址。该method属性只是指明了如何到达那里,在本例中是通过POST.
另一个重要部分是name每个输入标签上的属性,因为它决定了我们的params哈希值。上面我们有name="title"` <input>`、`<input>` name="description"、name="ingredients"`<input>` 和name="method"`<input>`,它们分别对应于我们 Recipe 模型中的属性。根据用户提交的内容,这将生成一个类似于这样的哈希值:
{
"title" => "Apple Pie",
"description" => "A recipe that I learned from my granny.",
"ingredients" => "4 apples, 1 cup sugar, ...",
"method" => "Preheat oven to 350 degrees. Cut the apples in small pieces..."
}
表单提交后,这些数据就存储在这个方便的params哈希表中了!现在让我们设置 POST 路由,并使用这些参数创建一个新的食谱:
# app/controllers/application_controller.rb
# This is responsible for PROCESSING a newly submitted recipe form
post '/recipes' do
@recipe = Recipe.new
# get data from params
@recipe.title = params[:title]
@recipe.descriptions = params[:descriptions]
@recipe.ingredients = params[:ingredients]
@recipe.method = params[:method]
@recipe.save
end
唯一的问题是,表单提交后,用户会看到一个空白页面。这里POST只是发送信息,之后并不会显示。在食谱数据处理完毕后,向用户显示最终的食谱是合理的。Sinatra 提供了一个便捷的redirect方法,可以将用户引导到另一个页面,在本例中就是食谱显示页面。将其添加到上述路由即可实现:
# app/controllers/application_controller.rb
post '/recipes' do
@recipe = Recipe.new
@recipe.title = params[:title]
@recipe.descriptions = params[:descriptions]
@recipe.ingredients = params[:ingredients]
@recipe.method = params[:method]
@recipe.save
# show post after completion
redirect "/recipes/#{@recipe.id}"
end
它redirect "/recipes/#{@recipe.id}"会将它发送到我们的食谱展示路线,get '/recipes/:id'这样用户就可以为自己的新作品感到自豪了。
好了,以上就是使用 MVC 概念搭建一个简单的基于 Sinatra 的应用程序的基本步骤!路由的功能非常丰富,想要了解更多信息,可以参考 Sinatra 的README 页面。
文章来源:https://dev.to/morinoko/the-basics-of-sinatra--la-mvc--configuration--routes--forms-417e