这篇文章主要分享博客里涉及的 Ruby, Rails,前端 CSS,JS,ubuntu 系统命令等知识。如果有什么不解的地方可以通过http://liuzhen.me页面下方的二维码扫描加我微信。
Ruby 是一种纯粹的面向对象编程语言。它由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)创建于 1993 年。
Ruby on Rails(官方简称为 Rails,亦被简称为 RoR),是一个使用 Ruby 语言写的开源 Web 应用框架,它是严格按照 MVC 结构开发的。它努力使自身保持简单,来使实际的应用开发时的代码更少,使用最少的配置。指南:https://ruby-china.github.io/rails-guides/
CSS 指层叠样式表,你在页面看到的展示效果都是通过 CSS 做出来的,页面的布局,字体大小,颜色,边框,菜单等等。详情可以查看:http://www.runoob.com/css/css-intro.html
JS 是属于网络的脚本语言,能做的事太多了,像我博客里的相册功能,时间线都是 JS 做出来的效果。
你可以通过搜索 Mac/windows/ubuntu install rails
来找到相关文档,这里提供 ubuntu 16.04 版本的安装文档:https://gorails.com/setup/ubuntu/16.04 , Mac 的安装文档:https://ruby-china.org/wiki/mac-nginx-passenger-rails
安装好 Rails 环境之后,你可以创建一个 Rails 项目了,如果你从来没用过 Rails,可以先用 15 分钟学习一下 Rails 入门, 了解 Rails MVC 结构。
如果你对 Rails 有一定的了解,可以按照这个模版 https://github.com/80percent/rails-template 提供的操作步骤,创建一个 Rails 项目,使用这个模版创建 Rails 项目的好处是,这个模版相当于一个全家桶,预先添加一个项目经常需要使用的 Gem 包,发布需要的 puma, mina, monit, nginx 配置文件,关于这几个东西是什么,有什么用后面会讲到。
启动 Rails
$ rails s
访问 localhost:3000
就能看到 hello world 页面了。
我的博客在设计之初只想要文章,相册,简历这几个功能,这三个功能比较相似,都有标题,内容和可有可无的描述。所以我就用了单表继承,建了个 base 表。
$ rails g model Base title:string content:text subtitle:string type:string
type 字段就是用于单表继承。
执行完这条命令之后,你会看到 db/migrate/xxxx_create_bases.rb
多了一个这样的文件,里面的内容是:
class CreateBases < ActiveRecord::Migration[5.1]
def change
create_table :bases do |t|
t.string :title
t.string :subtitle
t.text :content
t.string :type
t.timestamps
end
end
end
t.timestamps 是时间戳,系统会自动在这个表里面加上 created_at
, updated_at
两个字段。
添加完之后,需要把做一下数据迁移,我一开始学 rails 的时候对 数据迁移
这个词很不理解。其实数据迁移的意思就是,我们现在通过命令创建了个数据表的文件,但是这个文件没有被执行,不执行数据库里就还没有这张表,只有在执行了 rails db:migrate
之后,rails 才在数据库里把这张表给加上,这个操作就叫做 数据迁移。
创建完了 Base 表,现在就要创建文章表了 Article, 我们需要添加一个 app/models/article.rb
文件,写上:
class Article < Base
end
因为 Article 继承了 Base, 所以就拥有了 Base 的所有字段了。
你可以通过 rails c
从控制台输入 Article.new
可以看到:
irb(main):006:0* Article.new
=> #<Article id: nil, title: nil, subtitle: nil, content: nil, type: "Article", created_at: nil, updated_at: nil>
type 字段自动就是 Article, 这是 Rails 的一个特性,单表继承。
输入Article.all
, 看到的 sql 语句实际是从 bases 里查询 type 为 Article 的所以记录。
irb(main):007:0> Article.all
Article Load (127.0ms) SELECT "bases".* FROM "bases" WHERE "bases"."type" IN ('Article') LIMIT $1 [["LIMIT", 11]]
相册表Photo
,简历表Resume
和Article
的创建方式相同。
表创建好了,我们就可以创建Controller
了,Controller
需要区分前端和后端,前端就是提供给用户查询的,后台是提供自己添加,更新,删除操作的。另外后台因为是管理的地方所以不能让所有人都访问,所以需要设置成通过用户名和密码登录。这样别人就无法访问你的后台。
为了与前台有所区分,所以需要加一下命名空间:这里设置成 admin
. 先在 config/routes.rb
里添加路由,
Rails.application.routes.draw do
namespace :admin do
root 'dashboard#index', as: 'root'
resources :articles
resources :photos
resource :resume, only: [:edit, :update]
end
end
root 'dashboard#index', as: 'root'
设置后台的root
路由。使用 rails routes
命令可以查看具体的路由信息。
controllers
目录下添加admin
目录,这个目录下用于存放所有的后台文件,后台添加一个 app/controllers/admin/base_controller.rb
文件,继承了 ApplicationController
, 这么做是为了admin
下的所有controller
继承 Admin::BaseController
后,用户访问后端链接就会先校验当前用户是否登录,如果没有登录就跳转到登录页面。
代码如下:
class Admin::BaseController < ApplicationController
layout 'admin'
before_action :authenticate_user
def authenticate_user
unless session[:login]
redirect_to new_session_path
end
end
end
因为article
, photo
, resume
这几个功能比较相似,所以我只讲一下article
控制器:app/controllers/admin/articles_controller.rb
代码:
class Admin::ArticlesController < Admin::BaseController
def index
@articles = Article.all.order(created_at: 'DESC').page(params[:page])
end
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to admin_articles_path
else
render 'new'
end
end
def edit
@article = Article.find(params[:id])
end
def update
@article = Article.find(params[:id])
if @article.update(article_params)
flash[:notice] = '更新成功'
redirect_to admin_articles_path
else
render 'edit'
end
end
def destroy
@article = Article.find(params[:id])
if @article.destroy
flash[:notice] = '删除成功'
else
flash[:notice] = "删除失败, 原因: #{@article.errors.messages.to_s}"
end
end
private
def article_params
params.require(:article).permit(:title, :subtitle, :content)
end
end
控制台里面很简单,就是增,删,改,查。需要注意的就是redirect_to
, render
的区别,什么时候要用render
, 什么时候用redirect_to
.
render
是指直接熏染某个页面。
redirect_to
是指告诉浏览器,让浏览器再重新发送一个指定路由的请求操作。
如:create action
里写到如果保存成功就 redirect_to admin_articles_path
, 如果失败就 render 'new'
.
假如保存成功,就会告诉浏览器,让浏览器再向服务器发送一个admin/articles
路由请求,然后进入index action
里,查询所有Action
记录,再熏染index.html
页面,返回给浏览器。
假如保存失败,就用用户填写的@article信息熏染new.html
页面,并用 flash 里的信息,告诉用户提交失败的原因,如果失败后用 redirect_to new_admin_articles_path
,也能跳转到new
页面,但是用户提交的信息就没有了。
.row
.offset-md-2.col-md-8
= simple_form_for [:admin, @article] do |f|
= f.error_notification
= f.input :title
= f.input :subtitle
= f.text_area :content, id: 'editor_content', class: 'simditor', autofocus: true
= f.submit '提交', class: 'btn btn-primary'
= link_to '取消', admin_articles_path
javascript:
new Simditor({
textarea: $('#editor_content'),
toolbar: ['title', 'bold', 'italic', 'underline', 'strikethrough', 'fontScale', 'color', '|', 'ol', 'ul', '|', 'blockquote', 'code', 'table', 'link', 'image', 'hr', 'indent', 'outdent', 'alignment']
});
这个有点需要讲的是编辑器使用了simditor
插件,具体要加哪些信息可以看一下这个文档:http://simditor.tower.im/, 但是要支持上传图片功能需要在 admin 下添加一条路由:post '/upload', to: 'photos#upload'
, 在photos controller
里添加一个upload action
,把上传的图片保存到数据库。
前端的controller
继承ApplicationController
,前端的因为只设计到查询,所以添加路由的时候加上only
, 如:resources :articles, only: [:index, :show]
, 就只添加两条路由,如果不加only
默认会创建 7 条路由。
前端功能主要就涉及到 css.
css 调试步骤:
样式这里涉及的东西太多,我不一一讲解,只讲一些我认为值得讲一讲的知识点。如果想学习更多的 css 样式知识,可以在文章开头处提供的文档查看学习。
文章的展示对字体,间距,背景,颜色等等都要求很高,如果设计的不好,文章看久了就容易累,而且容易给别人一种不想去看的感觉。如果间距很窄,一大段落全是文字,就给人一种很不舒服的感觉。如果你对这些信息了解不多,不知道把这些值设置成多少比较好,也不要担心,找一个你觉得文字展示效果看起来很舒服的网站,打开他的控制台,看一下这个网站上这些信息设置的值是多少,跟着一样设置就行了。具体的细节可以再另做调整。
博客的页面底部用的 fa 字体,在gemfile
里添加 font-awesome-sass
后,就能展示出这些字体图标。但是目前的字体中没有支付宝的字体图标,你先不要看代码,想一想,如果是你,你要怎么实现一个跟 fa 字体相同效果的图标,这个图标带有 hover 效果,当鼠标放上去的时候背景变成了蓝色。
我的实现方法:
一开始我想的是用一个黑白图片代替,弄完之后我发现 hover 效果无法实现。于是我就用一个背景透明只有一个支字的图片代替,设置 border-radius,background-color 和字体达成一致效果,当鼠标放上去的时候就改变 background-color: #0085A1;
代码:
footer .fa-alipay {
border-radius: 50%;
margin-bottom: 4px;
background-color: #222529;
width: 41px;
}
footer .fa-alipay:hover {
background-color: #0085A1;
}
调试页面上关于hover
,visited
, focus
, active
效果,可以像图片中勾选来查看相应的样式效果。
时间线是用的一个 js 库,https://github.com/RyanFitzgerald/vertical-timeline, 具体可以查看文档。值得说一下的是,一开始看到这个时间线的效果是在一个网站看到的。然后我通过页面控制台,看到里面 class 名称命名很规范,所以感觉是个 js 库,直接在 google 搜索 cd-timeline-block
第一个结果就是这个库的信息。除了这种方式,还可以通过控制台的Sources
查看assets
文件信息,一般都是经常压缩的,但是有些外部库是有注释的,会写上这个是来自哪个库之类的信息。不过最简单快速的办法还是用 google 搜索来的快一点。如果你搜的 class 名字没有找到相应的信息,可以换个 class 名字试试。
另外一个要说的是,这个 JS 库里的一个 js 文件main.js
, 与 turbolink 一起加载,没生效,加载的时候就没被执行,然后我就把它用$(document).on 'turbolinks:load'
, 加载就好了。
博客里我最喜欢的就是这个相册功能了,当初也是看了这个翻书的效果,我才有重写博客的冲动。看到这个 js 库是在github Trending
上,这上面会推荐 github 上比较火的项目。这个库的地址:http://www.turnjs.com, 这里面提供了几个 demo. 这个 turnjs 用的 yepnope
加载 js,这么加载是因为,有些内容需要在其他文件加载之后去执行。但是有个问题是在生产环境这些 js 文件都是被转译了的。所以直接在yepnope
里面写上文件名,在生产环境上就会找不到对应的文件。对于这个我没有想到特别好的处理办法,就用
$('head').append('<%= javascript_include_tag 'turn.min.js' %>')
来加载文件,然后再执行yepnope({complete: loadApp})
。如果你有更好的办法可以交流一下。
效果支持手机端页面需要加上:meta width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no
前端和后端的 JS, CSS 尽量分开,这样加载速度会快一些
使用的外部库最好重新命名成可读名称,不然时间长了你就不知道这个库是干什么的了。比如我用了 timeline 的 JS 库,里面有个 main.js 的文件,我就把它重命名为 timeline-main.js, 这样不需要再加注释来说明这个文件是做什么的了。
发布需要在服务器上安装好 rails 后,配置nginx.conf
,一般放在/etc/nginx/conf.d/xxx.conf
文件。
puma.rb
,deploy.rb
配置文件是 mina 部署的相关配置信息,具体操作了什么可以通过bundle exec mina deploy -v
来查看:
$ bundle exec mina deploy -v
-----> Creating a temporary build path
-----> Server: liuzhen.me
-----> Path: /home/ruby/RBlog
$ echo "-----> Branch: master"
-----> Branch: master
-----> Using RVM environment "2.3.1"
-----> Quiet sidekiq (stop accepting new work)
-----> Fetching new git commits
$ (cd "/home/ruby/RBlog/scm" && git fetch "https://github.com/liuzhenangel/RBlog.git" "master:master" --force)
-----> Using git branch 'master'
$ git clone "/home/ruby/RBlog/scm" . --recursive --branch "master"
Cloning into '.'...
done.
-----> Using this git commit
$ git rev-parse HEAD > .mina_git_revision
$ git --no-pager log --format="%aN (%h):%n> %s" -n 1
liuzhenangel (eb06b54):
> update timeline
$ rm -rf .git
-----> Symlinking shared paths
-----> Installing gem dependencies using Bundler
$ bundle install --without development test --path "vendor/bundle" --deployment
-----> DB migrations unchanged; skipping DB migration
-----> Skipping asset precompilation
-----> Cleaning up old releases (keeping 5)
-----> Deploy finished
-----> Building
-----> Moving build to /home/ruby/RBlog/releases/41
-----> Build finished
-----> Launching
-----> Updating the /home/ruby/RBlog/current symlink
-----> Restart Puma -- hard...
-----> Stopping Puma...
-----> Starting Puma...
从这些日志信息可以看出,首先会根据你配置的服务器的域名和用户名 ssh 到服务器上,加载 ruby 环境,从 github 拉取最新代码,安装 gem 包,如果 css, js, 图片有更新就重新编译压缩 js, css, 图片,执行 db:migrate
数据迁移,首次发布还会执行rails db:create
来创建数据库。然后重启 puma.
nginx 相当于一个代理,当你在浏览器输入:http://liuzhen.me
的时候,先通过 DNS 找到这个域名对应的 IP,然后通过路由到达 IP 所在的服务器上,服务器发现用户请求的是 liuzhen.me,然后 nginx 就根据配置文件里配置的 liuzhen.me 找到对应的项目,这个 Rails 项目是由 puma 启动的,然后 nginx 就把这个事交给 puma, puma 收到后就根据路由去到对应的 controller,然后返回对应的页面给到浏览器。
没有 monit 也能发布成功,但是有时候你的 puma 可能异常关掉了,如果有 monit 的话,就能时刻监听这个进程是不是启动状态,一旦关闭了,就重新启动。
博客地址:http://liuzhen.me