Rails笔记
前言
Rails是用ruby语言写的,方便开发Web应用的框架.
采用了许多约定俗成的规则来实现MVC结构.(但也导致代码不好关联理解)
如果说还有一个更为简单的,约定俗成特例,
那就是为个人博客而构建的hexo,
同样是使用命令来产生文件,
也同样是用命令来开启服务器等.
从ORM的操作上可以看到laravel的影子.
似乎是有着较为完备的测试代码文档,
同时教程建议写代码以测试为导向.使重构变得自信一些.
安装rails
rails是ruby的一个包,使用ruby的包管理器 gem
安装,可以指定版本.
1 | printf "install: --no-rdoc --no-ri\nupdate: --no-rdoc --no-ri\n" >> ~/.gemrc |
当然arch系统不屑于使用ruby自带包管理器安装
需要了解的ruby的一些写法
ruby这个语言对于一种想法有多种表达,
因此才有了这些屁事.
各种各样的类语言化函数,以及各种不知道在哪看到就拿来用假装有逼格的表达
1 | a.first |
块
一行中表达为
1 | (1..5).each { |i| puts 2 * i } |
多行中表达为
1 | (1..5).each do |i| |
把{}替换为了do…end
散列(hash)和符号(symbol)
散列的举例和简写
1 | {"last_name" => "Hartl", "first_name" => "Michael"} |
其中的:last~name和~:first~name是符号~,个人认为本质是 字符串作为变量名时的简写
比如 "last_name"
可以写作 :last_name
调用时可以更明显, user["last_name"]
等于 user[:last_name]
同时散列中还可以简写一步, :last_name =>
简写作 last_name:
(注意冒号必须紧贴)
我猜可能和日语键盘不好打引号但容易打冒号有极大关系
实参不带括号
1 | arr.include?(b) |
符号的使用
1 | :name # 冒号多表示symbol或表示与=>相同的含义 |
其他简写
字符串数组方面去引号的表达
1 | :name |
事实上还有许多其他的%用法,见参考1.
hello world
初始化项目
1 | rails _5.1.6_ new hello_app |
产生了固定结构的文件夹:
├── app
│ ├── assets(css,js)
│ ├── channels(略)
│ ├── jobs(定时任务等)
│ ├── mailers(邮件相关)
│ ├── helpers(通用函数)
│ ├── controllers
│ ├── models
│ └── views
├── bin(rails自己需要的一些东西)
├── config
│ ├── application.rb
│ ├── database.yml
│ ├── locales
│ ├── routes.rb
│ └── ...
├── db
│ └── seeds.rb
├── Gemfile
├── Gemfile.lock
├── lib
│ ├── assets
│ └── tasks
├── log
├── package.json
├── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ ├── favicon.ico
│ └── robots.txt
├── README.md
├── storage
├── test
│ ├── application_system_test_case.rb
│ ├── controllers
│ ├── fixtures
│ ├── helpers
│ ├── integration
│ ├── mailers
│ ├── models
│ ├── system
│ └── test_helper.rb
├── tmp
│ ├── cache
│ └── storage
└── vendor
自动运行了 bundle install
命令,安装了需要安装的库.
与python不同的是,ruby的可以安装在非全局的位置中,
但此处貌似不能选择安装在本地.
引入更多外部的库
更改 hello_app/Gemile
,
安装一些包,指定版本等,举例如下
1 | source 'https://rubygems.org' |
学习这里的 group
表达.
不想安装生产环境才需要的gem可以使用如下命令.
1 | bundle install --without production |
如果没有特别需求可以不带参数.
初始的一些文件
请求先抵达路由
1 | # config/routes.rb |
root
即为网址根地址(比如http://localhost:3000/
)的别名application
是controller的名字hello
是controller中定义的方法
在简单的结构中,请求经过路由直接发送给后台controller.
这样后端会直接拿到前端的数据,无论是get还是post.
1 | # app/controllers/application_controller.rb |
这里的controller直接渲染了html文件
正常的话还会传给view层去渲染html文件.
运行
1 | rails server |
一般是运行在 http://localost:3000/
访问之即可发现 hello, world!
部署
略
基本操作兼名词解释
大多数的操作使用了rails作为命令,
rails命令的特点之一是参数可以使用简称.
比如 rails server
可写作 rails s
.
和hexo非常相似.
唯一麻烦一些的是版本指定的方格并不好.
以下相同含义命令的多种写法之间不再备注或者.
新建项目
1 | rails new app-name |
安装需要的gem
gem负责安装单个包,
bundle是包依赖管理器,在安装包的同时会安装有依赖关系的包.
在Gemfile中声明项目需要使用的包,然后安装
1 | 包安装在系统中 |
启动服务器
1 | rails s |
生成各种文件
使用 rails generate 文件类别 文件名 参数
生成需要的文件
使用 rails destroy 文件类别 文件名
撤销操作
-
利用脚手架直接产生所有文件
包括以下文件
- active~record相关文件~
- 定义数据库结构的文件
- model文件
- 模型测试相关
- 模型测试文件
- 数据注入文件(也称为固件)
- 资源文件(大致包含了resource概念的内容)
- 路由中的定义
- controller文件
- controller的测试文件
- helper文件(希望controller中只有业务代码,其他放在helper中)
- view层的各种文件
- html.erb
- scss
- coffee
activeRecord是rails的数据库工具,将数据库定义延伸使用到模型中.
(比如user对象有哪些成员不是看代码而是看数据库的定义.)
同时也有许多laravel风格的模型关联方法.此处以User模型举例,有两个字段分别是string类型的
1
rails g scaffold User name:string email:string
- active~record相关文件~
-
生成数据库迁移文件
1
2rails g migration add_index_to_users_email
rails g migration add_password_digest_to_users password_digest:string -
生成controller
1
2rails g controller StaticPages home help
rails g controller PasswordResets new edit --no-test-framework- 此处生成了名为 StaticPages的controller,其中有home和help两个方法
- 一般还会同时生成controller测试文件,除非声明不需要
- rails对命名有要求
- 命令行中使用大驼峰,文件名使用蛇形
- 在引用时有不写全名的习惯,比如在route中的controller名
- 对一些局部视图,默认文件名开头带下划线
- model使用单数(如
User.rb
),而controller和数据库等使用复数(users table, users~controller~)
- 此处生成了名为 StaticPages的controller,其中有home和help两个方法
-
生成model
1
2
3
4rails g model User name:string email:string
撤销
rails destroy model User -
生成测试
1
rails g integration_test site_layout
测试有一般有三种
- models
- controller
- integration
前两个一般随着主要文件的产生而产生,
可以视为unit测试 -
生成邮件程序
1
rails g mailer UserMailer account_actiivation password_reset
名为UserMailer的mailer有两个方法分别是account~activation和passwordreset~
-
生成图像上传程序
1
rails g uploader Picture
需要两个gem
- carrierwave
- mini~magick~
会生成文件
app/uploaders/picture_uploader.rb
数据库操作
-
迁移数据库
使用了生成的db/migrate/datestring~name~.rb文件
1
2
3
4
5
6
7
8rails db:migrate
指定环境
rails db:migrate RAILS_ENV=production
貌似支持管道风的使用方法,此处在迁移数据库结构后重置数据库内容
rails db:migrate:reset -
撤销迁移操作
1
2
3
4rails db:rollback
回到特定版本
rails db:migrate VERSION=0 -
植入数据
使用的是
db/seeds.rb
文件1
rails db:seed
打开控制台
常常用于练习active record的使用
1 | rails c |
开始测试
1 | rails t |
查看当前的路由
1 | rails routes |
其他名词解释
Rails的简单处理流程
- 浏览器发送请求
- 经过routes.rb
- 根据路由在controller中寻找合适的函数
- controller调用模型的方法
- 模型通过继承activeRecord类获得了和数据库交流的方法,
比如查询数据等等 - controller得到结果,更改变量的值,设定变量的值,
然后根据controller的设置决定是否继续渲染对应的html页面- rails可恶在渲染的页面不是手动指定的,只能看文件名
- 视图文件html.erb中可以使用controller中的变量,渲染html
- 用户看到结果
路由
路由的写法
-
简写案例1:
get ‘static~pages~/home’
get ‘/static~pages~/home’, to: ‘static~pages~#home’ (全称)
get ‘/home’, to: ‘static~pages~#home’ (更符合常理的url)
以get请求请求地址’static~pages~/home’,
使用static_pages_controller
的home
方法处理,
最后返回app/views/static_pages/home.html.erb
中内容. -
简写案例2
root ‘static~pages~#home’
root, to: ‘static~pages~#home’
get ‘/’, to: ‘static~pages~#home’ (全称)
具名路由
在get后写的路径,比如’/home’,可以在其他地方写成home~path来代替~.
另外还有一种写法是url,path和url的区别如下
root_path -> '/'
root_url -> 'http://www.example.com/'
‘static~pages~/home’,貌似只能使用static~pageshomeurl来指代~,
因home~path表示~‘/home’
带参数的具名路由
常用在测试中
1 | get edit_user_path(@user) |
- 这里使用user~controller的edit方法处理~
不过有时也正常使用
1 | redirect_to @user |
会自动调用user~controller的show方法~
resource和REST
如果路由中写
1 | resource :users |
则下列一系列的网址均可以使用
请求 | URL | 动作 | 作用 |
---|---|---|---|
GET | /users | index | 列出所有用户 |
GET | /users/1 | show | 显示id为1的用户 |
GET | /users/new | new | 显示创建新用户的页面 |
POST | /users | create | 创建新用户 |
GET | /users/edit | edit | 显示 ID 为 1 的用户的编辑页面 |
PATCH | /users/1 | update | 更新 ID 为 1 的用户 |
DELETE | /users/1 | destroy | 删除 ID 为 1 的用户 |
如果只想使用其中某些路由,则可以使用only
1 | resources :invoices, only: [:create, :show, :update, :destroy] |
REST的覆盖
有些地址,比如 /users/new
,
可能开发者更喜欢使用其他的名称,
比如 signup
,则
可能路由会写成
get ‘signup’, to: ‘users#new’
controller
各种过滤器
rails自带的好像是before~action~,
还有一些过滤器需要特定的gem才能工作
1 | # 使用destroy函数前,先调用admin_user函数获 |
include用法
当某个controller想要使用某模块中定义的函数时,
可以使用
1 | include SessionsHelper |
等形式引入该模块
render方法
rails的函数可以有多种返回值类型
- html
- json
其中html还可以根据语境使用对应的模板.
以下为举例
1 | render html: "hello, world!" |
- 若以上代码属于users~controller~.
则渲染的new都是app/view/users/new.html.erb
private方法
使用private关键字声明,
而后这些方法只能内部使用,
不能使用路由调用.
习惯上为了使private更明显,会将之后的函数全部再缩进一层
1 | private |
model
模型验证
-
使用validates添加标准验证
1
2
3validates :email, presence: true, length: { maximum: 255},
format: { with: VALID_EMAIL_REGEX},
uniqueness: { case_sensitive: false }对
email
属性进行验证- 不能为空或empty
- 长度最大255
- 需要符合特定格式([email protected])
- 在内存中需要唯一(无视大小写)
-
使用validate添加自定义验证
1
2
3
4
5
6
7
8
9
10validate :picture_size
private
# 验证上传的图像大小
def picture_size
if picture.size > 5.megabytes
errors.add(:picture, "should be less than 5MB")
end
end其中
picture
为要验证的属性,全称为self.picture
,
验证函数中可以使用该属性的各种信息,
如果验证出错,最后会在模型验证后的信息中添加 'picture should be less than 5MB’的信息 -
模型错误信息
user.valid? 返回true或false
user.errors 错误对象.
user.errors.messages 常用的错误信息样式,模型的字段为单位,每个后面跟着字符串数组
user.errors.details 类似message,不过每个字段后跟着的是字典组成的数组,不常用
user.errors.full~messages~ 也可能会比较常用,只是一个字符串数组,其中每句话都可以被自然理解 -
翻译文件及可以引用的变量
模型出错后在报错时,可以使用本地化文件渲染翻译.
翻译文件在src/config/locales/xx.yml
在报错信息中可以含有变量,
%{value}表示目前变量的值,
%{count}表示变量的目标值.
%{model}可能表示当前的model,
%{attribute}表示出问题的字段.
模型关联
现假设有users表,microposts表,和relationships表
一个user可以有多个microposts
一个user可以有多个active~relationships~(关注了其他人),
也因此,该user的following(被该user关注的人)有多个
1 | # 一个用户有许多微博,当删除该用户时,一并删除所有关联的微博 |
多个microposts可能只有一个user,
1 | belongs_to :user |
虚拟属性
一般模型中的字段与数据库中的字段是对应的,
有时当数据库中并不存在但为了使用方便需要为模型添加虚拟的字段.
比如当数据库中只存了xx~digest~(加密后的token)时,却想使用xx~token~,
可以使用以下方法.
1 | attr_accessor :remember_token, :activation_token, :reset_token |
过滤器
1 | # 在调用模型的save方法前执行的动作 |
其他放在前面的内容
1 | # carrierwave插件的方法,将图像和模型关联起来 |
view
erb
-
erb简介
嵌入式Ruby(简称ERb),可以在html代码中嵌入ruby代码,有点像jsp.
多数变量使用时会使用1
<%= xxx %>
-
在erb中可使用的表达
-
yield
原理上不是很清楚,但目前使用的地方有两个
-
使用变量
1
<title><%= yield(:title) %></title>
将页面的title设置为controller中设置的
title
变量的值 -
用作占位符
1
2
3<body>
<%= yield %>
</body>yield处会填充各个访问的页面,比如home.html.erb等,
这些放在application.html.erb下级的内容.
-
-
content~tag~
将某数据使用某tag表达出来
1
2<%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
<div class="alert alert-<%= message_type %>"><%= message %></div> -
link~to~
用于表达一个链接,
link~to的写法可能是因为Rails更喜欢使用具名路由而不是写死的编码~,
这样可以更改url而无需更改对应的视图文件的文件名.1
2<%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
<a href="/signup" class="btn btn-lg btn-primary">Sign up now!</a> -
render
1
2
3
4
5<!-- 直接指定要渲染的模板 -->
<%= render 'layouts/header' %>
<!-- 指定一个复数的模型,ruby会寻找单数模型的模板,然后循环渲染 -->
<%= render @users %> -
form~for~
Rails自带的一个生成表单的方法,比原生表单方便一些
传入一个ActiveRecord对象,则可以生成一个form.1
2
3
4
5
6
7
8
9
10
11
12
13
14<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
</div>
<%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>但也不一定传入ActiveRecord对象,
没有model文件的resource对象或者controller名也是可以的.
使用url可以指定按下按钮后提交的路径.
按说只要提交的参数与后台接收的参数能匹配即可.1
2
3
4
5
6<%= form_for(:password_reset, url: password_resets_path) do |f| %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %> -
flash
这是Rails一个很方便的组件,
效果是在页面上出现一个几秒钟的提示文本.
html,css,js等等全部写好了,只需要调用.比如提示成功可以使用success.
此外还可以使用danger,应该是和bootstrap的主题联系起来的.
还有一个flash是flash.now,用于在跳转后的页面中显示闪现消息.1
2flash[:success] = "Welcome to the Sample App!"
flash.now[:danger] = 'Invalid email/password combination'
-
-
视图
视图的文件名一般是
app/view/<model>s/xx.html.erb
通常调用可以是- controller中使用了对应方法xx后,自动使用对应的xx视图
- controller中的其他函数主动跳转到该视图
- 没登录时跳转到首页
- 改密码的update方法报错时使用edit的视图
- 两个有关联的功能没有各自的视图,合用一个,均使用render渲染同一个视图
-
局部视图
一般使用下划线作文件名开头,
比如app/views/layouts/_header.html.erb
调用时方法基本如下1
<%= render 'layouts/header' %>
- 省略了下划线
- 常常用于重复使用的场景
- header
- footer
- errors
Sass
Sass是一种编写CSS的语言,从多方面增强了CSS的功能
- 嵌套
- 变量
- 混入
Sass支持一种名为scss的格式,scss是css的超集,
没有引入新的语法,一个css就相当于一个没有变量的scss.
Rails使用Asset Pipeline的方式管理css和js文件,
默认使用sass语言编译scss文件到css文件.
然后在生产环境中,会将所有css合并到application.css,所有js合并到application.js.
如果项目使用了bootstrap(使用Less语言编写css),
则需要先借助 bootstrap-sass
gem先将Less转换成Sass才可以.
-
嵌套
1
2
3
4
5
6
7.center {
text-align: center;
}
.center h1 {
margin-bottom: 10px;
}写成
1
2
3
4
5
6.center {
text-align: center;
h1 {
margin-bottom: 10px;
}
}把
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
}
#logo:hover {
color: #fff;
text-decoration: none;
}写成
1
2
3
4
5
6
7
8
9
10
11
12
13
14#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
&:hover {
color: #fff;
text-decoration: none;
}
} -
变量
1
2
3
4
5
6
7
8
9h2 {
...
color: #777;
}
footer {
...
color: #777;
}写成
1
2
3
4
5
6
7
8
9
10
11$light-gray: #777;
h2 {
...
color: $light-gray;
}
footer {
...
color: $light-gray;
}如果使用了
bootstrap-sass
gem,
则可以直接使用其中定义好的变量,无需自行定义. -
混入(mixin)
更像是一段宏
1
2
3
4
5
6
7
8
9
10@mixin box_sizing {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.debug_dump {
...
@include box_sizing;
}
测试及测试导向
测试的分类
-
一般分3类
- models测试
- 命名上一般使用模型名(单数)~test~.rb,比如
user_test.rb
- 命名上一般使用模型名(单数)~test~.rb,比如
- controllers测试
- 命名上使用controller名~test~.rb,比如
users_controller_test.rb
- 命名上使用controller名~test~.rb,比如
- integration测试
- 命名上使用自定义测试名称~test~.rb,比如
users_login_test.rb
- 命名上使用自定义测试名称~test~.rb,比如
- models测试
-
其余还有功能上的一些测试比如
- helpers
- 用于测试放在helper文件中的通用函数
- mailers
- 用于测试邮件发送程序
- helpers
-
固件
还有注意
test/fixtures/models.yml
文件- 用于在测试前给测试用数据库填充数据,支持使用ruby的循环
- 命名上使用复数(可以理解为同数据库中表名)
测试驱动开发的介绍和优点
-
介绍
测试驱动开发(Test-Driven Development,简称TDD)
过程为- 编写测试(此时测试是失败的)
- 编写代码让测试通过
- 重构时更改代码,保持测试还是通过的
-
优点
- 避免回归问题(之前能用的功能改代码后不能用了)
- 重构有自信
- 一定程度上让开发者明确目标
-
建议
- 需求不明确可以先写代码再写测试
- 安全相关功能先写测试
- 明确知道要改的先不写测试(比如html的细节)
- 重构前写测试
大致的测试流程
- 使用test环境,产生
log/test.log
- 依照
test/fixtures/models.yml
中内容为测试数据库填充数据 - 开始跑测试文件
- 先跑setup函数,再跑某一个测试项目
- 先跑setup函数,再跑下一个测试项目
常用的测试语句
-
动作
-
请求地址
get static~pageshomeurl~
get login~path~
post login~path~, params: { test: content }
delete logout~path~
路由相关需要看路由 -
自行编写方法
在test~helper~.rb中自行编写函数,并用于测试
log~inas~(@user, remember~me~: ‘1’)
-
-
assert
-
用于判断真假值
assert flash.empty?
assert~not~ flash.empty? -
用于判断是否为空
assert~empty~ cookies[‘remember~token~’]
assert~notempty~ cookies[‘remember~token~’] -
用于判断是否相等
assert~equal~ 1, ActionMailer::Base.deliveries.size
-
用于判断返回的状况
assert~response~ :success
判断response的header是否为2xxassert~template~ ‘sessions/new’
assert~match~ content, response.body
判断response的body是否与template相同或是否与某字段匹配assert~select~ “a[href=?]”, login~path~, count: 2
判断得到的页面上是否有2个类似<x href=“/login”>的元素assert~redirectedto~ @user
判断是否跳转到某个页面 -
多用于测试数据库中
assert~nodifference~ ‘Micropost.count’ do
post microposts~path~, params: { micropost: { content: “” } }
end
判断执行post动作后Micropost.count的结果没有改变assert~difference~ ‘Micropost.count’, 1 do
post microposts~path~, params: { micropost: { content: content } }
end
判断执行post动作后Micropost.count的结果改变了
-
测试美化
使用 minitest-reporters
gem
并在 test/test_helper.rb
中添加如下
1 | ENV['RAILS_ENV'] ||= 'test' |
然后即可在使用 rails test
命令后以红绿色等更好看的格式显示结果
自动化测试
使用 guard
gem
使用以下命令生成guard配置文件 Guardfile
1 | bundle exec guard init |
在配置文件中写
1 | guard :minitest, spring: "bin/rails test", all_on_start: false do |
表示让Guard使用Rails提供的Spring服务器,从而减少加载时间,而且启动时不运行整个测试组件
同时需要把应该把 spring/
目录添加到 .gitignore
文件中,
防止sping和git产生冲突.
然后使用以下命令开始
1 | bundle exec guard |
书上说会监视文件的变化来自动测试,
但实测没有工作.
测试诊断
若为failed可以查看测试输出
若为error建议查看test.log
有时候某些测试失败,需要重启rails服务器才会成功
而还有一些时候出现错误,需要改项目的文件夹名才会成功
密码
简介
Rails默认使用的密码套件是 bcrypt
,
功能包括
- 加密字符串
- 为user模型添加虚拟属性
- 提供验证的函数
使用方法
-
安装bcrypt gem
-
在user model中使用
has_secure_password
- 为user增加了两个虚拟属性,password和password~confirmation~
- 为user增加了从password到password~digest的加密和保存方法~
-
编辑数据库
-
先生成迁移文件
1
2
3
4
5class AddPasswordDigestToUsers < ActiveRecord::Migration[5.2]
def change
add_column :users, :password_digest, :string
end
end -
然后使用
rails db:migrate
迁移数据库
-
-
自动加密并存储密码
1
2
3user = User.new(name: "Example User", email: "[email protected]",
password: "foobar", password_confirmation: "foobar")
user.save # 自动加密password并在数据库中存储为password_digest
bcrypt的其他功能
-
验证是否正确
1
BCrypt::Password.new(digest).is_password?(token)
-
有需要时加密字符串
1
2
3
4
5def digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
登陆
基于session的登陆
- 准备session控制器
- 准备好session/new,或者说login页面对应的html文件
- 验证成功后操作session变量即可
1 | module SessionsHelper |
- current~user方法定义并返回~
@current_user
变量的值.
即current~user~()的值同@current_user
,
因此可能会遇到current~user与~@current~user混用的情况~.
基于cookie的登陆
-
方案
使用以下方案实现持久会话
- 数据库中添加remember~digest~,用于保存令牌的摘要
- 生成随机字符串,用作记忆令牌
- cookie中存入
- 加密的用户id
- 记忆令牌
- 过期时间设置为未来的某个日期
- 数据库中存储加密后的令牌(或称令牌的摘要)
- 如果cookie中有用户的ID,就用这个ID在数据库中查找用户,
并且检查cookie中的记忆令牌和数据库中的哈希摘要是否匹配.
-
具体代码
数据库(模型)中操作
app/models/user.rb
1
2
3
4
5
6
7
8
9
10# 为了持久保存会话,在数据库中记住用户
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# 忘记用户
def forget
update_attribute(:remember_digest, nil)
end关于cookie的操作
app/helpers/sessions_helper.rb
1
2
3
4
5
6
7
8
9
10
11
12
13# 在持久会话中记住用户
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
# 忘记持久会话
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end在登陆或登出的同时一并进行记住不记住的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29# app/controllers/sessions_controller.rb
# 处理登陆的函数
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
+ params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
# app/helpers/sessions_helper.rb
# 退出当前用户
def log_out
+ forget(current_user)
session.delete(:user_id)
@current_user = nil
end
# 处理登出
# app/controllers/sessions_controller.rb
def destroy
+ log_out if logged_in?
redirect_to root_url
end注意
- 已经登录的才能退出
admin权限管理
使用以下方案实现自制的权限管理
-
在数据库中添加admin字段,boolean型,默认false(activeRecord会自动生成User.admin?方法)
-
在执行高危动作时先行判断: current~user~.admin?
或者使用1
2
3
4
5
6
7
8before_action :admin_user, only: :destroy
private
# 确保是管理员
def admin_user
redirect_to(root_url) unless current_user.admin?
end等形式进行限制
发送邮件
邮件也是系统的一个延伸,也需要有MVC结构
视图
纯文本版(app/views/user~mailer~/account~activation~.text.erb)
1 | UserMailer#account_activation |
html版(app/views/user~mailer~/account~activation~.html.erb)
1 | <h1>UserMailer#account_activation</h1> |
总的配置
1 | Rails.application.configure do |
定义发件人
1 | class ApplicationMailer < ActionMailer::Base |
定义发送动作
1 | class UserMailer < ApplicationMailer |
邮件的预览
在 test/mailers/previews/user_mailer_preview.rb
文件中
1 | # Preview all emails at http://localhost:3000/rails/mailers/user_mailer |
激活功能
方案
- 数据库中添加相关字段
- activation~digest~
- activated
- activated~at~
- 模型中添加
activation_token
虚拟属性 - 注册时生成user记录,生成令牌并加密后存入数据库
- 向用户发送未加密的秘钥
- 在
account_activations_controller
中添加验证并修改model的代码
实现代码
首先是user自身在创建前需要创建令牌
app/models/user.rb
1 | before_create :create_activation_digest |
然后是邮件的内容和发送
app/views/user_mailer/account_activation.text.erb
1 | Hi <%= @user.name %>, |
邮件中包含了需要访问的网址和生成的令牌.
1 | # app/mailers/user_mailer.rb |
然后是处理函数
app/controllers/account_activations_controller.rb
1 | def edit |
修改密码的功能
方案
- 数据库中添加字段
- reset~digest~
- reset~sendat~(主要用于30分钟失效)
- 模型中添加
reset_token
虚拟属性 - 当用户输入邮箱,验证正确后,生成令牌并加密后存入数据库
- 想用户发送未加密的秘钥
- 在
password_resets_controller
中添加验证并修改密码的代码
实现代码
类似激活
关于动态流
应该是一种视觉效果,
一个随时更新的列表,
就像微博.
通常为user类定义feed方法.
如果用户不关注任何人,
user.feed和user.microposts效果相同.
分页
使用 will_paginate
gem.
在页面上使用
1 | <%= will_paginate @users %> |
在controller中使用
1 | def index |