Part 7
-首先我们从整体的布局文件开始,开始美化之前我发现了一个明显的bug,就是当我点击一片文章显示内容时,再次点击导航栏的“文章列表”或者“关于博主”的时候,都会出现路由错误,原因是因为我在这些链接里面写的地址都是固定的url地址,而不是路由,所以导致每次访问都会在当前的页面url后加上那个url后缀,然后页面就会因为找不到路径挂掉,好,修改如下:
1.替换'Blog'链接为路由,由
<a class="navbar-brand" href="/">Blog</a>
改为
<%= link_to 'Blog', root_path, class: 'navbar-brand' %>
2.替换'文章列表'链接为路由,由
<li class="active"><a href="articles">文章列表</a></li>
改为
<li class="active"><%= link_to '文章列表', articles_path %></li>
3.替换'关于博主'链接为路由,由
<li><a href="about">关于博主</a></li>改为
<li><%= link_to '关于博主', about_path %></li>这段需要注意的是,<a>标签其实就是等同于link_to这个helper,但是当<a>里面还有CSS样式的时候,需要在最后添加一个class参数,类似class: 'navbar-brand'这样的。
-让我们来美化文章列表页面views->articles->index.html.erb,根据我们的要求,显示的应该是一个列表,类似于一个表格,所以我们的主体就是一个table,但依然是占据着col-sm-8这么宽的区域。所以,最开始的做完之后的代码如下:
<div class="row"> <div class="col-sm-8"> <table class="table"> <caption>文章列表</caption> <tbody> <% @articles.each do |article| %> <tr> <td><%= link_to article.title, article_path(article) %></td> <td><%= link_to '编辑', edit_article_path(article) %></td> <td><%= link_to '删除', article_path(article), method: :delete %></td> <td><%= article.created_at.to_s(:db) %></td> </tr> <% end %> </tbody> </table> </div> <div class="col-sm-4"> </div> </div>
大家可以看到,其实我们原来的“写博客”的链接不见了,因为我想把它放到table的右上角,但是没找到好办法,想了下,因为标题<caption>其实也是一行,那就是一个row,那就可以使用class=col-sm-6这样的class来控制,所以就改成这样了:
<caption> <div class="row"> <div class="col-sm-6" align="left">文章列表</div> <div class="col-sm-6" align="right"><%= link_to '新博客', new_article_path %></div> </div> </caption>好了,到此articles的index页面初步完成了。
-开始articles部分的new页面,我希望输入框的大小是不可以拖拽改变的。
那么同样,留出右边的1/3的空白区域,左边为生成文章的区域,所以这个页面的代码可以改成:
<div class="row"> <div class="col-sm-8"> <%= form_for @article, url: articles_path do |f| %> <div class="form_content"> <%= f.label :title, "文章标题" %> <%= f.text_field :title, class: "form-control" %> <%= f.label :content, "文章内容" %> <%= f.text_area :content, class: "form-control", style: "resize:none;", rows: 10 %> </div> <%= f.submit "提交", class: "btn btn-primary" %> <% end %> </div> <div class="col-sm-4"> </div> </div>输入框和文本框的class都是"form-control",文本框默认是可以拖拽放大缩小的,要想保持大小,设置style属性为"resize:none;"即可,关于文本框的高度,设定rows值比较合适
同理,edit页面同样是这样。
-开始about页面。这个页面仅仅就是一个文字段落,相对比较容易。修改完的代码如下:
<div class="row"> <div class="col-sm-8"> <p>博主主要从事的工作是iOS开发,从2011年一次偶然的机会,喜欢上Rails,从此就变成整天想着Rails但是干着iOS的程序员。时至2015年1月9日,实在不允许这样的情况持续下去,而且我热切期待自己能有一个完全属于自己的博客,所以就开始着这个项目的开发,仅以此记录开发的过程,如有问题,请来信告知。qingfengorlangyue#gmail.com</p> </div> <div class="col-sm-4"> </div> </div>
到此,博主的前端能力有限,心目中预计的第一步骤相关的美化工作结束,接下来就去掉一些冗余的代码,评论功能和页面的持续美化。
Part 8
-首先添加评论功能,所以我们需要设计一个Comment数据模型,以及一系列关于它的CRUD,除此之外,因为评论和文章是有对应关系的,所以还需要考虑它与article之间的关系。
从产品的角度看,每个文章都会有0~N条评论,而每条评论都只能属于某一片文章,所以看起来文章和评论之间的关系应该是1对多的关系。目前,以博主相对不成熟的Rails和前端技术,我就暂时设计评论的字段仅包含一个content来表示用户输入的内容,另外因为需要与article模型的联系,需要一个外键,所以总共是2个字段。
1.创建comment数据模型
rails generate model comment name:string article_id:integer在app->models里面就多了一个comment.rb模型文件,然后数据模型和数据库是对应起来的,所以会自动生成一个migrate文件,用户创建数据库表
2.指定与article数据模型的关系
article.rb文件中添加
has_many :comments
comment.rb文件中添加
belongs_to :article
刚发现原来我想要的comment的字段名字命错了,应该是content字段,却写成了name字段,所以在migrate目录下面相应的迁移文件中修改过来即可。接下来生成comment数据表
rake db:migrate好了数据模型确定了,开始为comment添加CRUD吧。
-创建comment控制器,命令如下:
rails generate controller comments做"CRUD"就又会设计到相应的action,也相对应的会需要template,但是comment是依赖于article的,所以它的路由不能像article那样是一个独立的model路由,它的所有操作都需要先查出它所属的article是谁,然后再操作其它内容,大部分时候,一片文章的内容和它的评论是同时出现的,所以在routes.rb中有关article和comment的部分需要改一下以支持这种关系:
由
resources:articles
改为
resources :articles do resources :comments, controller: 'comments_controller' end-提供创建comment的界面接口
修改views->articles->show.html.erb页面:
<div class="row"> <div class="col-sm-8"> <div class="article_content"> <h3><%= @article.title %></h3> <p><%= @article.content %></p> </div> <hr size="1" noshade="noshade" style="#cccccc dotted;"/> <textarea class="form-control form_content", style="resize:none", rows="5", placeholder="评论"></textarea> <div align="right"> <button class="btn btn-primary">发布评论</button> </div> </div> <div class="col-sm-4"> </div> </div>
从这里我们看到了进行评论的界面接口,但是你点击“发布”按钮是不会有什么反应的,因为这个按钮还未关联任何事件,从这里开始我们需要把他当作一个独立的部分进行操作和HTTP请求,所以
1)这里把它改为一个form表单,而不仅仅是一个textarea和button;
2)关于form表单,我们需要注意需要提交的参数对象和字段;
所以关于评论的这部分的代码可以替换为:
<%= form_for [@article, @article.comments.build] do |f| %> <div class="form_content"> <%= f.text_area :content, class: "form-control", style: "resize:none", rows: "5", placeholder: "评论" %> </div> <div align="right"> <%= f.submit "发布评论", class: "btn btn-primary", align: "right" %> </div> <% end %>
这里面要注意的一个地方是
[@article, @article.comments.build]这是这个form需要提交的参数,而且会把这个消息发送到comment控制器的create方法中,但因为comment是依附于文章的,而routes中的代码以及它们之间的has_many和belongs_to关系
resources :articles do # you can use the custom controller name for comment. # eg. resource :comments, controller: article_comment resources :comments end会让comment的route和普通的resource不一样,对比之前article的resource得到的对应方法和route:
Article.new Article.create Article.create!comment的resource对应的方法和route:
@article.comments.build @article.comments.create @article.comments.create!所以,这个form需要的内容除了comment对象本身还需要它关连的article对象。
那么相应的,我们需要在comment控制器中加入create方法来处理comment的创建。接着面临的问题就是创建完评论之后,界面该如何动作?通常还需要呆在当前页面,但是如果有关联的comment的话,则需要显示(可能是刚刚创建的)comment。所以article的show页面还需要加入显示comment的部分,这部分可以类似这样:
<% if @article.comments.any? %> <div class="article_content"> <table class="table" rules="groups"> <tbody> <% @article.comments.each do |comment| %> <tr> <td><%= comment.content %></td> <td align="right"><%= comment.created_at.to_s(:db) %></td> </tr> <% end %> </tbody> </table> </div> <hr size="1" noshade="noshade" style="#cccccc dotted;"/> <% end %>
在有comment的情况下再显示这个区域。
在开发过程中,我调试过程中经常需要从rails c的console中创建测试数据,也需要删除数据,创建我们说过了,那么删除的方式类似这样:
# 删除表中所有数据 Comment.delete_all # 删除一条数据 comment = Comment.last comment.delete
好了,到这里我们就可以正常的针对具体的文章发布评论了。
Part 9
到这里,大家可以看到整个页面我们一直在做左边的2/3部分,右边的部分没有做过,所以接下来我们就来设计右边的1/3部分的内容。
-首先更改welcome的index页面,这个页面需要显示所有的评论,而且需要是倒序的,代码如下:
<% @comments.each do |comment| %> <div class="article_content"> <%= link_to comment.content, article_path(comment.article) %> </div> <% end %>
所以,我们需要在welcome的controller中获取到所有经过倒序排列的comment:
def index @articles = Article.all @comments = Comment.all.reverse_order end
但我发现,除了这个页面,我希望所有的页面都是在最右边显示这个部分,所以我需要为每个页面添加如下代码!这样将会非常的繁琐,而且其实失眠很多的网站的页面都是这的布局,那他们也是这么做的吗?其实有一个叫做“局部视图”的技术可以解决这个问题,就是解决复用的问题的。好,让我们开始使用它吧!
-首先看welcome的index页面,把我们刚刚加入的那个部分的代码粘贴到一个新的文件里面去,这个文件是app->views->welcome->_comment.html.erb然后把index里面的那段代码替换成
<%= render "comment" %>然后你可以刷新一下看看,构建完代码之后,是同样的效果,好的,那我么就把它加到每个需要的页面中去吧!
-在article的index页面,去掉原来的
<div class="col-sm-4"> </div>替换为:
<%= render "./welcome/comment" %>
注:因为_comment.html.erb是放在welcome目录中的,而在搜索局部视图的时候,render后面的其实是一个相对路径,所以这里我们需要改变一下,把它的路径换成welcome中的路径,否则就会寻找当前目录下的_comment.html.erb文件,而因为找不到就报错了。
-在article的show页面,做同样的修改即可。
-welcome的about页面
-article的new页面
-article的edit页面
做完这些你会发现你需要在几乎每个对应页面的action中添加一行代码
@comments = Comment.all.reverse_order
这也是一种代码的冗余,rails依然有比较好的做法可以让代码优化,我们可以使用before_action来达到这一目的。before_action的意思是你在执行这个指令所包含的所有方法之前都需要之行另外一个在这里规定好的方法。
1.修改welcome控制器,在所有方法的最前面添加如下代码:before_action :all_comments, only: [:index, :about]
这意味着在index和about方法执行前,先要执行all_comment方法,这个方法的定义如下:
def all_comments @comments = Comment.all.reverse_order end
其实就是获取了所有的comments,没什么特别的,当没有:only字段来限定的话,是对所有的方法都起作用的
2.同样的修改article控制器,在最前面添加
before_action :all_comments, only: [:index, :show, :edit, :new]
好了,到这里,这是我能够想到的所有的代码优化部分。
Ok,又发现一处可以去掉冗余代码的地方,就是article的new.html.erb 和 edit.html.erb页面中关于form的部分。
在article的目录中新建一个_form.html.erb,把相同的部分
<div class="form_content"> <%= f.label :title, "文章标题" %> <%= f.text_field :title, class: "form-control" %> <%= f.label :content, "文章内容" %> <%= f.text_area :content, class: "form-control", style: "resize:none;", rows: 10 %> </div>
相应的new和edit中相同的代码可以替换为
<%= render partial: "form", locals: { f: f } %>
这次的复用和上次优点不同,多了一个参数locals,而且前面也多了个叫partial的symbol,这是因为这次我们需要传递一个参数到局部视图中去,不然局部视图就不知道里面的那个“f”是什么意思了,而一旦需要传递参数,前面就需要添加partial这个symbol来保证参数的形式一样。
好了,接下来的工作就是网站的安全性部分,包括防意外脏数据的插入,字段不为空的检测,格式要求,和最主要的测试部分。之所以把测试放在最后,并不是什么所谓的TDD和BDD,是因为我们初学者需要成果的展示,需要成就感,仅是个人的观点,所以我就这么做。