1 Sorcery的安装和设置
2 Sorcery具体使用
3 Bootstrap和FontAwesome Gems的安装和使用
4 Sidekiq安装和使用
5 Sidekiq深入使用以及生产环境注意事项
6 Unicorn的安装和使用
7 Capistrano的安装和使用
8 Capistrano自定义部署任务: Unicorn进程管理自动化
9 怎样通过一个shell脚本来部署项目
10 unicorn进程管理之unicorn-worker-killer gem的使用
11 eventmachine介绍和怎样实现一个并发的多任务进程
12 使用ruby和eventmachine创建一个生产环境可用的进程任务:reaper
13 使用sassc-rails来加快scss文件的编译速度
1 Sorcery的安装和设置
Sorcery功能提供类似于Devise,地址,devise支持多模型,但是sorcery只是支持单模型,一般情况下,一个模型就可以了,在这个模型中通过字段属性决定模型的角色,相对而言,sorcery是轻量级的,devise是重量级别的。
安装和查看:
#安装gem
gem 'sorcery'
#显示gem的安装地址
bundle show sorcery
#使用下面方式可以查看多少功能能使用rails g功能
rails g -h #显示rails g sorcery:install
#使用下面方式可以查看sorcery:install安装哪些???rails g sorcery:install -h
#使用,默认使用user这个model
rails g sorcery:install
#默认生成user模型,为模型添加字段就是给user模型添加字段
rails g migration add_username_to_users username:string
#改变默认的user模型为person模型,后面必须为Person单数形式,数据库中为people复数形式
rails generate sorcery:install --model Person
#添加相关???一般情况添加下面??榫涂梢粤?,不过需要下一节的config配置。
rails g sorcery:install remember_me reset_password user_activation --only-submodules
配置文件/config/initializers/sorcery.rb,这些地方需要配置才行,目的是把激活功能删除,减少配置的复杂度
user.user_activation_mailer = nil
user.activation_mailer_disabled = true
user.prevent_non_active_users_to_login = false
2 Sorcery具体使用
sorcery只有针对密码的加密功能,但是没有针对密码的校验和密码一致性的校验。执行上面的??榻⒎绞剑?/p>
rails g sorcery:install remember_me reset_password user_activation --only-submodules
config/initializers/sorcery.rb参考如上的配置方式。
建立model/user.rb文件代码
class User < ApplicationRecord
authenticates_with_sorcery!
attr_accessor :password, :password_confirmation
validates_presence_of :email, message: "邮箱不能为空"
validates :email, uniqueness: true
validates_presence_of :password, message: "密码不能为空",
if: :need_validate_password
validates_presence_of :password_confirmation, message: "密码确认不能为空",
if: :need_validate_password
validates_confirmation_of :password, message: "密码不一致",
if: :need_validate_password
validates_length_of :password, message: "密码最短6位",
minimum: 6,
if: :need_validate_password
private
def need_validate_password
self.new_record? ||
(!self.password.nil? or !self.password_confirmation.nil?)
end
end
1、上面的代码说明,必须在user.rb文件添加authenticates_with_sorcery!方法,这个是sorcery默认必须放在User类中执行的类方法。默认建立的两个虚拟属性是password和password_confirmation两个属性,sorcery中保存的加密字段是crypted_password,sorcery针对解决的是password的加密,而没有针对password验证的过程,所以上面的代码很多是针对密码的验证。
2、针对need_validate_password的方法的说明,这个方法在第一次见的时候比较迷惑,经过探索,自己的理解如下。正常的web页面中针对密码的更改和针对用户名等一些信息的修改是放在不同的用户界面中,同时password和password_confirmation是虚拟属性(区别于实际属性,更新的情况下会从数据库取值,而虚拟属性不会从数据库中取值),每一次用户属性更新的情况下,如果没有need_validate_password的方法,那么每次都会执行相应的验证操作,而正常的web页面其实密码更新和用户其他信息的更新是分开的。所以体现了need_validate_password方法的必要性。
3、这个方法说明,关于密码的验证的前提条件,针对新的字段,或者是针对密码进行更新的情况下(密码进行更新意味着密码肯定不为空)。
关于sorcery方法的说明:sorcery自定义的方法,一般是应用于model层和controller层,但是例外是helper方法,既可以应用于controller层中,也可以应用于view层中。比如如下两个:
logged_in?
current_user
关于注册controller文件users_controller.rb的说明:
下面的内容分别对应的是上面在刚开始创建的时候建立的模块。
class UsersController < ApplicationController
def new
@user = User.new
end
#用户注册
def create
@user = User.new(params.require(:user)
.permit(:email, :password, :password_confirmation))
if @user.save
flash[:notice] = '注册成功,请登录'
redirect_to new_session_path
else
render action: :new
end
end
#用户密码修改
def change_password
if current_user.valid_password?(params[:old_password])
current_user.password_confirmation = params[:password_confirmation]
if current_user.change_password!(params[:password])
render json: {status: 'ok', msg: '密码修改成功'}
else
render json: {status: 'error', msg: current_user.errors.messages.values.flatten}
end
else
render json: {status: 'error', msg: '旧密码不正确'}
end
end
#用户发送忘记密码邮件,这里需要对邮件进行配置
def send_forget_password_email
@user = User.find_by(email: params[:email])
if @user
@user.deliver_reset_password_instructions!
render json: {status: 'ok', msg: "重置密码的链接已发送到上面的邮箱"}
else
render json: {status: 'error', msg: '指定邮箱不存在'}
end
end
#密码重置
def reset_password
@user = User.load_from_reset_password_token(params[:token])
if @user
@user.password_confirmation = params[:password_confirmation]
if @user.change_password!(params[:password])
render json: {status: 'ok', msg: '密码修改成功'}
else
render json: {status: 'error', msg: @user.errors.messages.values.flatten}
end
else
render json: {status: 'error', msg: 'token不正确或者已过期'}
end
end
end
关于用户登录controller文件sessions_controller.rb文件的创建
class SessionsController < ApplicationController
before_action :auth_user, except: [:destroy]
def new
end
def create
#login是sorcery的登录方法
if user = login(params[:email], params[:password])
flash[:notice] = '登陆成功'
redirect_to root_path
else
flash[:notice] = "邮箱或者密码错误"
redirect_to new_session_path
end
end
def destroy
#logout是sorcery的退出方法
logout
flash[:notice] = "退出登陆"
redirect_to root_path
end
private
def auth_user
#logged_in?是sorcery的判断登录方法
if logged_in?
redirect_to root_path
end
end
end
登录界面
<h1>Sessions#new</h1>
<%= form_tag sessions_path, method: :post, class: "form-horizontal" do %>
<div class="form-group">
<div class="col-lg-12">邮箱</div>
<div class="col-lg-12">
<%= text_field_tag :email, nil, class: "form-control" %>
</div>
</div>
<div class="form-group">
<div class="col-lg-12">密码</div>
<div class="col-lg-12">
<%= password_field_tag :password, nil, class: "form-control" %>
</div>
</div>
<div class="form-group">
<div class="col-lg-12">
<div class="pull-right">
<a href="<%= new_user_path %>">注册</a>
</div>
</div>
</div>
<div class="form-group">
<div class="col-lg-12">
<input type="submit" name="sign_submit" value="登陆" class="col-xs-12 btn btn-primary">
</div>
</div>
<% end -%>
注册界面
<h1>Users#new</h1>
<div>
<% unless @user.errors.empty? %>
<ul>
<% @user.errors.messages.values.flatten.each do |error| %>
<li><%= error %></li>
<% end -%>
</ul>
<% end -%>
</div>
<%= form_for :user, url: users_path, method: :post, html: { class: 'form-horizontal', id: "user_form"} do |f| %>
<div class="form-group">
<div class="col-lg-12">邮箱 *</div>
<div class="col-lg-12">
<%= f.text_field :email, class: "form-control" %>
</div>
</div>
<div class="form-group">
<div class="col-lg-12">密码 *</div>
<div class="col-lg-12">
<%= f.password_field :password, class: "form-control" %>
</div>
</div>
<div class="form-group">
<div class="col-lg-12">密码确认 *</div>
<div class="col-lg-12">
<%= f.password_field :password_confirmation, class: "form-control" %>
</div>
</div>
<div class="form-group">
<div class="col-lg-12">
<input type="submit" name="create_user_submit" value="创建账户" class="col-xs-12 btn btn-primary">
</div>
</div>
<% end -%>
3 Bootstrap和FontAwesome Gems的安装和使用
bootstrap的安装和使用在工具篇中已经做了介绍。
fontawesome是一个图标库??梢员坏弊隼嗨谱痔迨褂?。这里介绍fontawesome的安装和使用。
#gemfile文件
gem 'font-awesome-rails'
#application.scss #使用scss文件可以使用@import语法
@import "font-awesome"; #@import语法说明是引入该scss文件
#使用font-awesome
<i class= "fa fa-gear"></i>
使用响应式布局需要在application.html.erb中添加
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
4 Sidekiq安装和使用
背景知识1:安装和启动redis(目前稳定版本是3.2.6)
#安装和编译
$ wget http://download.redis.io/releases/redis-3.2.7.tar.gz
$ tar xzf redis-3.2.7.tar.gz
$ cd redis-3.2.7
$ make
#启动,在编译之后的src文件夹中执行下面操作
$ redis-server
上面这个方式会占用一个终端,通过下面的方式启动redis的同时重新打开一个终端
redis-server &
#进行交互
$ src/redis-cli #开启redis客户端
redis> set foo bar
OK
redis> get foo
"bar"
#关闭redis,打开redis-cli,输入
shutdown
sidekiq的配置和使用
#原理
web端生成异步任务,然后将这些任务存储在redis数据库中,sidekiq从redis数据库中读取这个异步任务,并且执行。
#gemfile文件
gem 'sidekiq'
#config/application.rb
#active_job是rails针对异步操作统一规定的interface
config.active_job.queue_adapter = :sidekiq
#开启redis数据库
redis-server #需要在redis的src文件目录下,sidekiq默认连接的是redis数据库
redis-client shutdown #关闭redis数据库,或者CTRL+C
#开启sidekiq服务
sidekiq -C config/sidekiq.yml -e development
sidekiq -C config/sidekiq.yml -e development -d #运行放在后台
#sidekiq的配置文件config/sidekiq.yml
:concurrency: 5
:pidfile: tmp/pids/sidekiq.pid
development:
:concurrency: 2
production:
:concurrency: 5
:queues:
- default
- [mailers, 2]
:logfile: log/sidekiq.log
#对应config/sidekiq.yml文件建立sidekiq.pid文件
tmp/pids/sidekiq.yml #参照目录结构建立空文件
#通过rails g job异步框架以及相应的代码形式
1.默认形式
rails g job guests_cleanup
class GuestsCleanupJob < ApplicationJob
#:default显示的是使用默认队列,队列的优先级和名称可以设置
queue_as :default
def perform(*guests)
# Do something later
end
end
2.可以实现设置队列的名称
rails g job guests_cleanup --queue urgent
#显示代码,省略形式
queue_as :urgent
#执行异步任务,前提是启动redis和sidekiq
参考上面已经建立的队列,在rails c平台下面进行执行:
GuestsCleanupJob.perform_later("abc")
执行结果如下:
<GuestsCleanupJob:0x007fbd0c589230 @arguments=["abc"],
@job_id="830cd076-33dc-4825-9238-37b94f607bcc",
@queue_name="default", @priority=nil,
@provider_job_id="9797351c-c8a2-4c51-a42f-b6822bdfbbc4">
立即执行如下结果:
Performing GuestsCleanupJob from Async(default) with arguments: "abc" Performed GuestsCleanupJob from Async(default) in 11.41ms
上面的结果会立马执行,通过下面的方式会延时执行
GuestsCleanupJob.set(wait: 5.minutes).perform_later("abc")
#通过ui界面查看异步任务,需要编辑config/routes.rb文件
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq-stat'
#通过如下的链接访问sidekiq界面
http://0.0.0.0:3000/sidekiq-stat
#需要注意的问题
1. sidekiq是不会重载的,如果建立了新的队列,需要重启sidekiq
使用sidekiq来发送邮件
第一部分介绍发送邮件部分,之前的总结中已经存在需要更新的地方
#与controller的使用规则类似,在终端运行下面的代码
rails g mailer UserMailer
#在user_mailer.rb文件中建立如下的代码:
class ApplicationMailer < ActionMailer::Base
default from: "zhengjiajun121@gmail.com"
def welcome_email
mail(to: "zheng_jiajun@foxmail.com", subject: "Welcome to My Awesome Site")
end
end
#app/views/user_mailer下面建立两个视图文件,分别是welcome_email.html.erb文件和welcome_email.text.erb文件:
#进行文件配置,在config/environments/developments.rb中添加如下代码:
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.gmail.com",
port: 587,
user_name: "zhengjiajun121",
password: "xxx",
authentication: "plain",
enable_starttls_auto: true }
#在终端执行下面的文件,邮件可以从gmail邮箱发送至foxmail邮箱中:
UserMailer.welcome_email.deliver_now
第二部分发送邮件结合sidekiq和active_job
class GuestsCleanupJob < ApplicationJob
#:default显示的是使用默认队列,队列的优先级和名称可以设置
queue_as :default
def perform(*guests)
UserMailer.welcome_email.deliver_now
end
end
#终端执行下面代码
GuestsCleanupJob.set(wait: 5.minutes).perform_later
5 Sidekiq深入使用以及生产环境注意事项
1.异常处理
6 Unicorn的安装和使用
passenger,unicorn是基于进程的模式,puma是基于线程的模式。passenger和unicorn的区别在于前者有企业服务,运维比较方便,后者是完全开源的。
#添加的gem
gem 'unicorn'
#添加unicorn的配置文件config/unicorn.rb,注释部分是nginx的配置文件
# http://unicorn.bogomips.org/
# startup:
# unicorn_rails -E production -p 8081 -c config/unicorn.rb -D
#
# ngxin.conf
#
# user nginx;
# worker_processes 2;
# error_log /var/log/nginx/error.log;
# pid /run/nginx.pid;
# events {
# worker_connections 1024;
# }
# http {
# log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
# access_log /var/log/nginx/access.log main;
# sendfile on;
# tcp_nopush on;
# tcp_nodelay on;
# keepalive_timeout 30;
# types_hash_max_size 2048;
# include /etc/nginx/mime.types;
# default_type application/octet-stream;
# underscores_in_headers on;
# # Load modular configuration files from the /etc/nginx/conf.d directory.
# # See http://nginx.org/en/docs/ngx_core_module.html#include
# # for more information.
# include /etc/nginx/conf.d/*.conf;
# gzip on;
# gzip_http_version 1.1;
# gzip_vary on;
# gzip_comp_level 5;
# gzip_types text/css text/html application/x-javascript application/javascript text/javascript application/json;
# gzip_buffers 16 8k;
# client_max_body_size 20m;
# upstream app {
# server localhost:3000 fail_timeout=0s;
# }
# server {
# listen 80;
# server_name doman.com;
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# root html;
# }
# root /mnt/www/app/current/public;
# # try_files will find the $uri in the given file list, if not found it will hand it to @app handler
# try_files $uri/index.html $uri @app;
# location ~* \.(xml|txt|html|htm|ico|css|js|gif|jpe?g|png)(\?[0-9]+)?$ {
# expires max;
# try_files $uri/index.html $uri @app;
# }
# location @app {
# proxy_set_header Host $host;
# proxy_set_header X-Forwarded-Host $host;
# proxy_set_header X-Forwarded-Server $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_buffering on;
# proxy_pass http://app;
# }
# }
# }
rails_env = ENV["RAILS_ENV"] || "development"
rails_root = File.expand_path(__FILE__).split('/')[0..-3].join('/')
port_number = 3000
process_number = rails_env == 'production' ? 2 : 1
puts "unicorn env: #{rails_env}"
puts "unicorn port: #{port_number}"
puts "unicorn process number: #{process_number}"
preload_app true
working_directory rails_root
pid "#{rails_root}/tmp/pids/unicorn.pid"
stderr_path "#{rails_root}/log/unicorn.log"
stdout_path "#{rails_root}/log/unicorn.log"
listen port_number, :tcp_nopush => true
# listen "/tmp/unicorn.ruby-china.sock"
worker_processes process_number
timeout 30
before_fork do |server, worker|
# the following is highly recomended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
#
# LL NOTE:
# => master node will not serve http request, so it's no use to hold the database connection.
# => https://devcenter.heroku.com/articles/concurrency-and-database-connections
#
if defined?(ActiveRecord::Base)
ActiveRecord::Base.connection.disconnect!
end
end
after_fork do |server, worker|
if defined?(ActiveRecord::Base)
ActiveRecord::Base.establish_connection
end
end
#启动
unicorn_rails -E production -p 8081 -c config/unicorn.rb -D
7 Capistrano的安装和使用
13 使用sassc-rails来加快scss文件的编译速度