This post showcases the web application I created as part of the curriculum for the Software Engineering program at Flatiron School. This application is an iteration of a simple content management system that implements CRUD (Create, Read, Update, Delete) functionality inside a MVC (Model, View, Controller) design paradigm. This application utilizes the web application library “Sinatra” to facilitate communication between components of the application and the web. Sinatra is built on Rack and provides the convenience of handling HTTP requests and responses. This app also utilizes ActiveRecord in order to provide an interface that allows the application to connect to a database using the relational database management system “SQLite3”. ActiveRecord aids in building database tables, connecting models to database tables and defining relationships between models.
这篇文章展示了我作为Flatiron学校软件工程计划课程的一部分创建的Web应用程序。 此应用程序是简单内容管理系统的迭代,该系统在MVC(模型,视图,控制器)设计范例中实现CRUD(创建,读取,更新,删除)功能。 该应用程序利用Web应用程序库“ Sinatra”来促进应用程序组件与Web之间的通信。 Sinatra基于Rack构建,并提供了处理HTTP请求和响应的便利。 该应用程序还利用ActiveRecord来提供一个界面,该界面允许应用程序使用关系数据库管理系统“ SQLite3”连接到数据库。 ActiveRecord有助于构建数据库表,将模型连接到数据库表以及定义模型之间的关系。
介绍 (Introduction)
The application I built is called “NatParkRev” and its purpose is to provide a place for user-generated reviews of national parks. There are 62 national parks in the United States and no site is available that specifically tracks past and present user reviews for all of those parks. I get a lot of enjoyment visiting national parks so I thought it would be useful to have an application that allows people to read recent reviews of the park they intend on traveling to prior to their visit.
我构建的应用程序称为“ NatParkRev”,其目的是为用户生成的国家公园评论提供场所。 美国有62个国家公园,没有可用网站专门跟踪所有这些公园的过去和现在的用户评论。 访问国家公园给我带来了很多乐趣,因此我认为拥有一个允许人们在访问前阅读打算游览的公园的近期点评的应用程序将非常有用。
建立 (Setup)
There are ten Ruby gems required to run this application:
要运行此应用程序,需要十个Ruby gem:
gem 'pry'
— a runtime development console that allows the developer to freeze code on particular lines.gem 'activerecord', :require => 'active_record'
— facilitates the use of objects to be persisted to the database by providing methods and associations.gem 'sinatra'
— domain-specific language that handles HTTP requests and responses.gem 'sinatra-activerecord', :require => 'sinatra/activerecord'
— provides access to ActiveRecord helper methods and Rake tasks.gem 'sinatra-flash'
— provides a way to implement messages that are carried over to the redirected route.gem 'rake'
— allows common tasks to be created and run in the terminal.gem 'shotgun'
— provides automatic reloading of the rackup config.ru
command by running shotgun
.gem 'bcrypt'
— stores, salts and hashes user passwords.gem 'sqlite3'
— database management system.gem 'require_all'
— allows for one line to load all files and subdirectories inside a folder.
gem 'pry'
运行时开发控制台,允许开发人员在特定行上冻结代码。 gem 'activerecord', :require => 'active_record'
通过提供方法和关联来方便使用将对象持久化到数据库中。 gem 'sinatra'
处理HTTP请求和响应的特定于域的语言。 gem 'sinatra-activerecord', :require => 'sinatra/activerecord'
提供对ActiveRecord助手方法和Rake任务的访问。 gem 'sinatra-flash'
提供一种方法来实现传递到重定向路由的消息。 gem 'rake'
允许创建常见任务并在终端中运行。 gem 'shotgun'
-提供的自动载rackup config.ru
通过运行命令shotgun
。 gem 'bcrypt'
存储,添加盐和哈希用户密码。 gem 'sqlite3'
数据库管理系统。 gem 'require_all'
允许一行加载文件夹中的所有文件和子目录。
A Rakefile
was created to manage tasks during development. In this file, the environment.rb
file is loaded and the sinatra/activerecord/rake
library is loaded to provide access to many useful rake tasks, including: db:create_migration
, db:migrate
, db:drop
and db:seed
.
创建了一个Rakefile
来管理开发期间的任务。 在此文件中,将加载environment.rb
文件,并且将加载sinatra/activerecord/rake
库,以提供对许多有用的rake任务的访问,包括: db:create_migration
, db:migrate
, db:drop
和db:seed
。
require './config/environment'
require 'sinatra/activerecord/rake'desc 'starts Pry with loaded environment'
task :console do
Pry.start
end
In order to setup the database, migration tables were created for :posts
, :parks
, :users
, :characteristics
, and :post_characteristics
. The :post_characteristics
table is a join table in which columns post_id
and characteristic_id
are foreign keys. By using the rake db:create_migration
task command, a new file is created with the file name as the class name. These files represent the tables in the database. Each line below the create_table
line of code below represents the columns in the database. The :posts
table has six columns in it, including two foreign keys for the user
and park
id’s.
为了设置数据库,为:posts
, :parks
, :users
, :characteristics
和:post_characteristics
创建了迁移表。 :post_characteristics
表是一个:post_characteristics
表,其中post_id
和characteristic_id
列是外键。 通过使用rake db:create_migration
任务命令,将以文件名作为类名创建一个新文件。 这些文件代表数据库中的表。 下面代码的create_table
行下面的每一行代表数据库中的列。 :posts
表中有六列,包括用于user
和park
ID的两个外键。
After all three migration tables are created, the rake db:migrate
task command is run which actually creates the tables in the database.
创建所有三个迁移表后,将运行rake db:migrate
task命令,该命令实际上在数据库中创建了表。
A seed file was created in the database directory which loads in data to help aid when building the app. The seed file creates User, Post, Park and Characteristic objects, and associates the objects with each other.
在数据库目录中创建了一个种子文件,该文件会加载数据以帮助构建应用程序。 种子文件创建“用户”,“张贴”,“停放”和“特征”对象,并将这些对象彼此关联。
楷模 (Models)
In order to associate objects with each other, ActiveRecord was used. ActiveRecord is the “M” or “model” part of the MVC paradigm. The model is responsible for the representation and manipulation of data in the application. The model is where the main logic of the application is configured. The controller will typically request objects from the model. The model will then request data from the database.
为了使对象彼此关联,使用了ActiveRecord。 ActiveRecord是MVC范例的“ M”或“模型”部分。 该模型负责应用程序中数据的表示和处理。 该模型是配置应用程序的主要逻辑的地方。 控制器通常会从模型中请求对象。 然后,该模型将从数据库中请求数据。
This application has five models: User, Post, Park, Characteristic, and PostCharacteristic. All models are represented as classes. Each model class inherits from ActiveRecord::Base
which provides additional methods for these classes. The model class is where associations are created and validations are determined.
该应用程序具有五个模型:用户模型,发布模型,停放模型,特征模型和后特征模型。 所有模型都表示为类。 每个模型类都继承自ActiveRecord::Base
,后者为这些类提供了其他方法。 在模型类中创建关联并确定验证。
Users are associated with the other models as follows: 1. A User has many posts.2. A User has many parks through posts.
用户与其他模型的关联如下:1.一个用户有很多帖子; 2。 用户通过帖子有很多公园。
Posts are associated with the other models as follows:1. A Post belongs to a user.2. A Post belongs to a park.3. A Post has many post characteristics.4. A Post has many characteristics through post characteristics.
帖子与其他模型的关联如下:1。 帖子属于用户2。 邮局属于公园3。 帖子具有很多帖子特征4。 通过帖子特征,帖子具有许多特征。
Parks are associated with the other models as follows:1. A Park has many posts.2. A Park has many users through posts.
公园与其他模型的关联如下:1。 一个公园有很多职位2。 一个公园通过帖子吸引了很多用户。
Characteristics are associated with the other models as follows:1. A Characteristic has many post characteristics.2. A Characteristic has many posts through post characteristics.
特征与其他模型的相关性如下:1。 特征具有很多后期特征2。 特征通过帖子特征具有许多帖子。
PostCharacteristics are associated with the other models as follows:1. A PostCharacteristic belongs to a post.2. A PostCharacteristic belongs to a characteristic.
后特性与其他模型关联如下:1.。 后特性属于职位2。 后特性属于特征。
A User is validated by the presence of the name, email and username attributes, in addition to the uniqueness of the user’s username. The has_secure_password
macro is called which provides additional methods for validation, and include #authenticate
, #password
and #password=
. This macro not only requires the presence of a password, but will authenticate the password against a password that is saved in the database which has been salted and hashed by bcrypt
.
除了用户名的唯一性之外,还通过名称,电子邮件和用户名属性的存在来验证用户。 所述has_secure_password
宏被调用,其提供了一种用于验证附加的方法,并且包括#authenticate
, #password
和#password=
。 该宏不仅需要密码,而且还会根据保存在数据库中的密码对密码进行身份验证,该密码已被bcrypt
加密和散列。
There are two methods in the User model class. The first is the instance method #slug
which transforms the username of an instance of User to a slugged version which yields a string of all lower case letters with hyphens inserted where there were once spaces. The second method is a class method .find_by_slug
which iterates over all of the User instances and finds where the slugged username is equivalent to the slugged name that is being searched for. This method is useful when a user of the app is requesting a dynamic route that leads to /users/:username
.
User模型类中有两种方法。 第一个是实例方法#slug
,它将User实例的用户名转换为#slug
版本,该版本会产生所有小写字母的字符串,并在其中有空格的地方插入连字符。 第二种方法是类方法.find_by_slug
,该方法遍历所有User实例,并找到所键入的用户名与正在搜索的所键入的名称等效的位置。 当应用程序的用户正在请求通往/users/:username
的动态路由时,此方法很有用。
观看次数 (Views)
The “V” or “view” part of the MVC paradigm is the user-facing portion of the application. This is what the user sees and interacts with as they navigate the app. There are three subdirectories within the views folder, which are: /parks
, /posts
and /users
.
MVC范例的“ V”或“视图”部分是应用程序的面向用户的部分。 这是用户在浏览应用程序时看到并与之交互的内容。 views文件夹中有三个子目录: /parks
, /posts
和/users
。
The main review page is located in the /posts
subdirectory with a filename of index.erb
. Views are written as .erb
files which consist of HTML and embedded Ruby code. The main review page is the view where all user-generated reviews are displayed to the user of the application. It is important to note that while you do not have to be logged in to view this page, you are required to be logged-in in order to read the actual review.
主审阅页面位于/posts
子目录中,文件index.erb
。 视图被编写为.erb
文件,该文件由HTML和嵌入式Ruby代码组成。 主审阅页面是一个视图,其中所有用户生成的审阅都显示给应用程序的用户。 重要的是要注意,虽然您不必登录才能查看此页面,但是您必须先登录才能阅读实际的评论。
Two drop down menus are displayed on this page which allows the user to filter reviews by a specific national park or state. Embedded Ruby code is denoted by the substitution tag, <%=
and scripting tag, <%
. The <select>
html tag is utilized to create drop-down menus. The filter by park <select>
form iterates over the @parks
array, which is an array of all Park instances, Park.all
. Each iteration will create an option with that specific park’s route as the value. Each iteration, aptly named park
, gets the #slug
method called on it which provides a streamlined route name. The drop-down menu display for each option is the park name plus the number of posts associated with that park. An unless
statement is included so that if there are no posts with that park, the number of posts will not be displayed so it is easy for the user to discern which parks have reviews and which do not.
此页面上显示两个下拉菜单,允许用户过滤特定国家公园或州的评论。 嵌入式Ruby代码由替换标记<%=
和脚本标记<%
。 <select>
html标记用于创建下拉菜单。 park <select>
表单的过滤器在@parks
数组上进行迭代,该数组是所有Park实例Park.all
的数组。 每次迭代都会创建一个选项,以该特定公园的路线作为值。 每次迭代(恰当地命名为park
)都会调用#slug
方法,该方法可提供简化的路线名称。 每个选项的下拉菜单显示为公园名称以及与该公园关联的帖子数。 包括unless
声明,以便如果该公园没有帖子,则不会显示帖子的数量,因此用户可以轻松辨别哪些公园有评论,哪些没有评论。
The main review page also displays the actual table of every review in the database. This table iterates over the @posts
array, which is an array of all created Post objects, Post.all
. The posts are ordered by the datetime
they were updated, in descending order, so that the most recent reviews are displayed at the top of the table. Each iteration creates a table row with various table data cells. Getter methods are called on each post
which exposes and displays the necessary information.
主评论页面还显示数据库中每个评论的实际表。 该表遍历@posts
数组,该数组是所有已创建的Post对象Post.all
。 这些帖子按它们的更新datetime
时间降序排列,以便最新评论显示在表格顶部。 每次迭代都会创建一个包含各种表格数据单元的表格行。 每个post
调用Getter方法,以公开和显示必要的信息。
Another important view page is posts/new.erb
. This file holds the form for creating a new review. Users are required to fill in all fields on the form which include a title, national park, content and rating. There is also an optional checkbox field for characteristics. Since a timestamps
column was included when creating the database table, the created_at
and updated_at
attribute is automatically populated when a new review is saved to the database. The content field uses the textarea
html tag so that a multi-line text field is displayed. The name and value attributes will be available to the developer when the form is submitted via the params
hash. The contents of the hash can be manipulated in various ways using nested hashes and arrays.
另一个重要的视图页面是posts/new.erb
。 该文件包含用于创建新评论的表格。 要求用户填写表格上的所有字段,包括标题,国家公园,内容和等级。 还有一个用于特性的可选复选框字段。 由于创建数据库表时包括timestamps
列,因此将新评论保存到数据库时,将自动填充created_at
和updated_at
属性。 内容字段使用textarea
html标记,以便显示多行文本字段。 通过params
哈希提交表单时,开发人员可以使用name和value属性。 哈希的内容可以使用嵌套的哈希和数组以各种方式进行操作。
By setting name="post[content]"
and not setting the value (since the user will enter in the value), the params
hash will be a hash with “post” as the key, along with a nested hash with a key of “content”. This allows the developer to gain access to all of these key/value pairs and use mass assignment when transforming this data into objects later on in the controllers.
通过设置name="post[content]"
而不设置值(因为用户将输入值), params
哈希将是一个以“ post”为键的哈希值,以及一个嵌套键为“内容”。 这样,开发人员就可以访问所有这些键/值对,并在以后将这些数据转换为对象时在控制器中使用批量分配。
The users/login.erb
view displays a form to the user for when they want to log in to the application. This is a POST request as the user will be sending information back to the server. The username
and password
are requested of the user and this information is then validated once this request is received by the appropriate route and controller.
users/login.erb
视图向用户显示表单,以供用户登录到应用程序时使用。 这是一个POST请求,因为用户将信息发送回服务器。 向username
请求username
和password
,然后在适当的路由和控制器收到此请求后,便会验证此信息。
控制器 (Controllers)
Controllers are responsible for orchestrating the connection between the browser and the application. They are essentially conductors in an orchestra. Controllers receive requests from the client and routes the requests to the appropriate action. The controller then requests the data from the model, which in turn pulls data from the database. Once the models receive the data, the data is manipulated and sent back to the controller as objects. The controller then sends the objects to the view for rendering. The view will render the HTML which will be to the client as a response by the controller.
控制器负责协调浏览器和应用程序之间的连接。 他们本质上是乐队的指挥。 控制器从客户端接收请求,并将请求路由到适当的操作。 然后,控制器从模型中请求数据,该模型又从数据库中提取数据。 一旦模型接收到数据,就将数据作为对象进行操作并发送回控制器。 然后,控制器将对象发送到视图以进行渲染。 该视图将呈现HTML,该HTML将作为控制器的响应发送给客户端。
There are four controllers in this application: ApplicationController, ParksController, PostsController and UsersController.
此应用程序中有四个控制器:ApplicationController,ParksController,PostsController和UsersController。
The ApplicationController inherits from Sinatra::Base
. All other controllers inherit from the ApplicationController which allows all controllers to have access to everything that ApplicationController possesses, including Sinatra::Base
. The ApplicationController configures settings which control the features that get enabled. Cookie-based :sessions
is enabled to allow users to stay logged into the application without re-entering credentials on every new page that’s loaded. The :views
and :public_folder
are set to designate specific directories for the location of the view and css/image files.
ApplicationController继承自Sinatra::Base
。 所有其他控制器都继承自ApplicationController,后者允许所有控制器访问ApplicationController拥有的所有内容,包括Sinatra::Base
。 ApplicationController配置用于控制启用功能的设置。 基于Cookie的:sessions
已启用,以允许用户保持登录到应用程序的状态,而无需在加载的每个新页面上重新输入凭据。 :views
和:public_folder
设置为视图和CSS /图像文件的位置指定特定目录。
The ApplicationController also has helper methods defined to help the other controllers function properly and more efficiently. These include #logged_in?
, #current_user
, #redirect_if_not_logged_in
, #redirect_if_not_post_owner
, and #authenticate_and_change_password
.
ApplicationController还定义了帮助程序方法,以帮助其他控制器正常且更有效地运行。 这些包括#logged_in?
, #current_user
, #redirect_if_not_logged_in
, #redirect_if_not_post_owner
和#authenticate_and_change_password
。
The #authenticate_and_change_password
method allows a user to change their account information and utilizes the #authenticate
method. The #authenticate
method is provided by ActiveRecord::Base
and is made available by calling the has_secure_password
macro in the User model. When #authenticate
is called on a particular instance of User
, the password that is passed in (from the user’s input when the login form is submitted) is salted, hashed, and then compared to the salted and hashed password that is stored in the database. If the two version match each other, the return value will be that instance of User
. If they do not match, then the return value will be false. At no point is the user’s password ever revealed since the password_digest
column name in the users
database table is encrypted with bcrypt
.
#authenticate_and_change_password
方法允许用户更改其帐户信息并使用#authenticate
方法。 #authenticate
方法由ActiveRecord::Base
提供,通过在用户模型中调用has_secure_password
宏使其可用。 当在特定的User
实例上调用#authenticate
,传入的密码(提交登录表单时从用户输入中传入)将被加密,散列,然后与存储在数据库中的加密散列密码进行比较。 如果两个版本彼此匹配,则返回值将是User
该实例。 如果它们不匹配,则返回值将为false。 由于使用bcrypt
加密了users
数据库表中的password_digest
列名,因此永远不会泄露用户的密码。
The PostsController is responsible for all requests pertaining to posts (reviews). When a new review is created, the post '/posts'
route is called to action. This route will:
PostsController负责与帖子(评论)有关的所有请求。 创建新评论时,将调用post '/posts'
路线以采取行动。 该路线将:
- Confirm that the user is logged in. 确认用户已登录。
Create a new instance of
Post
by passing in the params hash with the key of:post
.通过使用
:post
键传递params哈希来创建Post
的新实例。- Assign and associate the new post’s user to the current user. 分配新帖子的用户并将其关联到当前用户。
- Save the newly created post and redirect if the post successfully saves. 保存新创建的帖子,如果帖子成功保存,则重定向。
An example of a get request is the get '/posts/:id/edit'
route. This route is responsible for displaying the edit form to the user and will:
获取请求的一个示例是get '/posts/:id/edit'
路由。 此路线负责向用户显示编辑表单,并将:
- Confirm that the user is logged in. 确认用户已登录。
Find the particular post that is being requested to be edited by searching for the
:id
attribute in the database that corresponds to the params hash:id
that is part of the dynamic url route.通过在数据库中搜索对应于params hash
:id
的:id
属性,查找要编辑的特定帖子,该参数是动态url路由的一部分。- Confirm that the current user is associated as the owner of the post. 确认当前用户已关联为帖子的所有者。
Render the
posts/edit
view.渲染
posts/edit
视图。
Once the edit form is filled out correctly, the user can submit the form. The action of the form designates which route is requested. The edit form will send a POST
request to the controller, BUT in actuality this is a patch
request. A “hidden” input field is created inside the <form>
tag of the edit view which designates this request as a patch
request instead of a traditional POST
request.
正确填写编辑表单后,用户即可提交表单。 表单的动作指定了请求的路线。 编辑表单将向控制器发送一个POST
请求,但实际上这是一个patch
请求。 在编辑视图的<form>
标记内创建一个“隐藏”输入字段,该字段将该请求指定为patch
请求,而不是传统的POST
请求。
The associated patch '/posts/:id
dynamic route is called to action which will:
关联的patch '/posts/:id
动态路由被调用来执行以下操作:
- Confirm that the user is logged in. 确认用户已登录。
Find the particular post that is being requested to be edited by searching for the
:id
attribute in the database that corresponds to the params hash:id
that is part of the dynamic url route.通过在数据库中搜索对应于params hash
:id
的:id
属性,查找要编辑的特定帖子,该参数是动态url路由的一部分。- Confirm that the current user is associated as the owner of the post. 确认当前用户已关联为帖子的所有者。
Redirect to the
/posts/:id
route if the updated post saves in the database.如果更新的帖子保存在数据库中,请重定向到
/posts/:id
路由。
The UsersController is responsible for all routes pertaining to user actions. One useful route is the get '/users/:username'
dynamic route which is a GET
request to a particular user page. This route will:
UsersController负责与用户操作有关的所有路由。 一种有用的路由是get '/users/:username'
动态路由,它是对特定用户页面的GET
请求。 该路线将:
- Confirm that the user is logged in. 确认用户已登录。
Find the particular user instance in the database via the dynamic
:username
url. The class method.find_by_slug
is utilized here which iterates through theUser.all
array and searches for where the user’s username converted to slug form matches the passed in slug form of the username (which is pulled from the params hash).通过动态
:username
URL在数据库中找到特定的用户实例。 这里使用类方法.find_by_slug
遍历User.all
数组,并搜索转换为slug格式的用户名与传入的slug形式的用户名(从params哈希中提取)相匹配。Render the
/users/show
page only if the user is found and the user’s username is equivalent to the session’s username.仅在找到用户并且用户名与会话的用户名等效时,才渲染
/users/show
页面。
The post '/login'
route utilizes the #authenticate
method and will:
post '/login'
路由使用#authenticate
方法,并将:
Find the
User
instance by searching the database for username that the user entered in the form.通过在数据库中搜索用户在表单中输入的用户名,找到
User
实例。- Confirm that the user instance exists and that the password entered into the form matches the password in the database. 确认用户实例存在,并且输入到表单中的密码与数据库中的密码匹配。
Set the
:username
key of the session hash to that particular user’susername
.将会话哈希的
:username
密钥设置为该特定用户的username
。- Redirect to the previously sought after URL if one was set, or redirect to the user’s page if conditions are met. 如果设置了URL,则重定向到先前寻求的URL,如果满足条件,则重定向到用户页面。
最终产品 (The Final Product)
CSS was implemented after coding the backend of the application. The layout.erb
file holds the template HTML. A navigation bar was implemented so that only relevant options are displayed to the user, depending on if they are logged in or not. Flash messages were integrated into the controllers to provide the user with useful information when navigating the site. The navigation bar and flash message logic are found in the layout.erb
file.
CSS是在对应用程序的后端进行编码之后实现的。 layout.erb
文件包含模板HTML。 实施了导航栏,以便根据是否登录,仅向用户显示相关选项。 Flash消息已集成到控制器中,以便在导航站点时为用户提供有用的信息。 导航栏和Flash消息逻辑可在layout.erb
文件中找到。
Screen captures of the working application are provided below.
下面提供了正在运行的应用程序的屏幕截图。
The Github repository for this application can be found here:https://github.com/dougschallmoser/nat-park-rev-sinatra-app
可以在以下位置找到此应用程序的Github存储库: https : //github.com/dougschallmoser/nat-park-rev-sinatra-app