Study hard and make progress every day!

2020-06-29
Postgresql和Mysql中的事务隔离

事务的定义

事务拥有四个重要的特性:

  • 原子性(Atomicity)
    • 事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。
  • 一致性(Consistency)
    • 指事务将数据库从一种状态转变为另一种一致的的状态。事务开始前和结束后,数据库的完整性约束没有被破坏。
  • 隔离性(Isolation)
    • 要求每个读写事务的对象对其他事务的操作对象能互相分离,即该事务提交前对其他事务不可见。 也可以理解为多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。
  • 持久性(Durability)
    • 事务一旦提交,则其结果就是永久性的。即使发生宕机的故障,数据库也能将数据恢复,也就是说事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
      这四个特性就是常说的数据库的ACID 特性
阅读此文

2020-06-23
Crontab中解决sorry, you must have a tty to run sudo的方法

问题

crontab中使用sudo切换用户执行命令sudo -u user command时,会报sudo: sorry, you must have a tty to run sudo的错误。

原因

在一些Linux发行版中,会默认设置运行sudo需要在tty中运行。以CentOS 7.0为例,在/etc/sudoers中, 有一个默认设置Defaults requiretty

1
2
3
4
5
#
# Disable "ssh hostname sudo <cmd>", because it will show the password in clear.
# You have to run "ssh -t hostname sudo <cmd>".
#
Defaults requiretty

阅读此文

2020-06-21
Mac下调节外置显示器亮度的工具

起因

家里缺台显示器,趁着狗东6.18活动,入手了一台AOC U2790PQU(穷人表示买不起Dell U2720)。

到货后拆箱试用,检测了下屏幕,没有坏点, 而且在mac下即插即用,屏幕自动适应缩放,不用特殊设置缩放。一切都挺好。

唯一的问题是,AOC这台显示器亮度太亮了,我已经在显示器设置中将亮度设为0了,屏幕亮度还是太亮。

有问题就得尝试解决,于是上网搜索解决方案。

需要解决的问题

问题: 显示器亮度太亮,物理调节亮度为0后,黑暗中还是太亮。

网上搜了一圈,找到了4个在MacBook Pro上实现亮度调节的方法,记录如下:

相关说明

网上有说有些方法在某些系统上不起作用。因此贴一下我这边的系统配置, 下面的记录基于如下系统配置。
电脑型号: MacBook Pro (13-inch, 2018, Four Thunderbolt 3 Ports)
操作系统版本: macOS Mojave 10.14.4 (18E226)

阅读此文

2020-06-14
Docker基础

Docker安装

Docker安装(CentOS 7)

Docker在CentOS 7上的安装,直接参考官网安装手册即可: Install Docker Engine on CentOS

安装Dockerd主版本的步骤如下,要按照特定版本的,参考Docker官网。

1
2
3
4
5
6
7
sudo yum -y install epel-release
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# sudo yum list docker-ce --showduplicates | sort -r
sudo yum -y install docker-ce docker-ce-cli containerd.io -y
sudo systemctl start docker
systemctl enable docker

执行docker version来验证docker是否正常安装。

Tips:
也可以换用阿里云的docker镜像源yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

阅读此文

2020-06-09
一些常用的Nginx+Passenger配置

摘记一些常用的nginx和passenger(Passenger + Nginx)配置

Basic验证

测试环境的服务要放到互联网上时,如果为了避免测试服务的内容被搜索引擎收录,或者只提供给部分用户试用时,可以设置Basic Auth来限制访问。Basic Auth可以在应用层做,也可以在Nginx侧简单的设置。

如下是Nginx侧的设置方法, 摘录自Nginx官网文章Restricting Access with HTTP Basic Authentication

设置htpasswd

  1. 确保apache2-utils (Debian, Ubuntu) 或者 httpd-tools (RHEL/CentOS/Oracle Linux)已经安装。
  2. 创建htpasswd文件, 使用-c来创建新文件。如下是给用户名carl生成.htpasswd文件,在提示信息中输入访问密码。

    1
    2
    3
    4
    5
    $ sudo htpasswd -c /etc/nginx/.htpasswd carl
    New password:
    Re-type new password:
    Adding password for user carl
    $
  3. 如果要创建新的用户,那么去掉-c参数,往htpasswd文件中添加用户

    1
    2
    3
    4
    5
    $ sudo htpasswd /etc/nginx/.htpasswd shen
    New password:
    Re-type new password:
    Adding password for user shen
    $
  4. 确认.htpasswd文件中是否包含了刚设置的用户carl和shen

    1
    2
    3
    4
    $ cat /etc/nginx/.htpasswd
    carl:$apr1$ofR4vRu4$RwMK.HQawTD8/v.YOA2/d.
    shen:$apr1$c1vlrfgt$TqDy9JDsNcGyfTzcSOPLG0
    $
阅读此文

2020-06-06
屏幕知识概观

阅读此文

2020-05-31
在MacOS上加速Minecraft Launcher

状况

周末小朋友在家玩Minecraft,可能是近期网络环境有什么变动。官方启动器下载更新文件特别的慢。十分钟进度条才动了一小格子。

看着进度条好久也不更新,小朋友渐渐地就变得烦躁起来。

我把梯子开了全局代理,重新启动了Minecraft客户端,但貌似没效果。流量压根不从梯子走。

后来自己折腾了一下,找到了一个方法,记录如下:

方法步骤

  1. 从App中点开Minecraft,然后开启Terminal,找到启动器的进程信息

    1
    2
    3
    4
    5
    $ ps -ef | grep -i minecraft
    501 11957 1 0 3:45PM ?? 0:00.21 /Applications/Minecraft.app/Contents/MacOS/launcher
    501 11959 11957 0 3:45PM ?? 0:01.98 /Applications/Minecraft.app/Contents/Minecraft Updater.app/Contents/MacOS/nativeUpdater --nativePath /Applications/Minecraft.app --nativeStartupPath /Applications/Minecraft.app/Contents --nativeExecutable /Applicati ons/Minecraft.app/Contents/MacOS/launcher
    501 12917 12152 0 3:46PM ttys014 0:00.00 ggrep -i minecraft
    $
  2. 在Terminal中启动HTTP proxy(我本地翻墙的proxy端口绑定的是8001端口,可根据实际情况进行修改。)

    1
    $ export http_proxy="http://127.0.0.1:8001"; export HTTP_PROXY="http://127.0.0.1:8001"; export https_proxy="http://127.0.0.1:8001"; export HTTPS_PROXY="http://127.0.0.1:8001"
  3. 关闭GUI的启动器界面, 然后手动在Terminal中启动Launcher

    1
    2
    3
    4
    5
    6
    $ /Applications/Minecraft.app/Contents/MacOS/launcher
    2020-05-30 15:48:55.733 launcher[14037:45644] Claimed it found a path: /Applications/Minecraft.app/Contents/MacOS/launcher (/Applications/Minecraft.app/Contents/MacOS/launcher) 4096
    https://launchermeta.mojang.com/v1/products/launcher/022631aeac4a9addbce8e0503dce662152dc398d/mac-os.json
    2020-05-30 15:48:58.496 nativeUpdater[14040:45717] Found executable path: /Applications/Minecraft.app/Contents/MacOS/launcher
    2020-05-30 15:48:58.506 nativeUpdater[14040:45717] Found executable path: /Applications/Minecraft.app/Contents/MacOS/launcher
    https://launchermeta.mojang.com/v1/products/launcher/022631aeac4a9addbce8e0503dce662152dc398d/mac-os.json
  4. 此时,也会启动GUI画面的启动器,但是下载文件时,启动器就会走proxy了。

阅读此文

2020-05-27
Docker添加国内镜像源

在国内访问Docker Hub的速度有点慢,换国内源会稳定和快一点。

国内的几个源:

设置方法

参照Using Container Registry’s Docker Hub mirror这边描述,共有这么几个设置方法

  1. 在配置文件中添加registry-mirrors的配置。启动时自动配置,Linux下的默认配置文件是/etc/docker/daemon.json.

    1
    2
    3
    {
    "registry-mirrors": ["https://<my-docker-mirror-host>"]
    }
  2. 手动启动dockerd的时候,添加--registry-mirror参数

    1
    dockerd --registry-mirror=https://<my-docker-mirror-host>
阅读此文

2020-05-14
Ruby & Rails Tips


Struct & OpenStruct

Struct

Struct 用于快速声明一个类。

1
2
3
4
5
6
Person = Struct.new(:age, :name, :sex)
me = Person.new(24, 'Spirit', 'male')
me.age # => 24
me.name = 'Test'
me.name # => 'Test'
me.height # => NoMethodError

优点: 快速声明和实现
缺点: 只能响应定义的字段,不能响应任意的属性

OpenStruct

参考Ruby Ruby 中的 OpenStruct 详解
OpenStruct可以自由设置set和get的一个类

1
2
3
4
5
p = OpenStruct.new()
p.name='hello'
p.name # p.name就是hello
p.age=12
p.age # p.age就是12


skip before_action


API接口中使用cookies的方法

方法一: 在Controller中添加include ActionController::Cookies

1
2
class ApplicationController < ActionController::API
include ActionController::Cookies

在Controller中就可以访问到cookies变量了。

方法二: 如果不想引入过多的方法,可以在Controller中定义cookies方法

1
2
3
4
def cookies
# helpers not available in --api mode
request.cookie_jar
end


Rails 4中memory_store缓存的使用

Rails 4中memory_store类型的基础用法

配置

需要在config/environments/development.rb或者config/environments/production.rb中设置开启cache

  • config.action_controller.perform_caching设置为true
  • config.cache_store设置为:memory_store

命令

所有方法参考: ActiveSupport::Cache::Store on Ruby on Rails 4.0.13

  • 写入Cache,使用Rails.cache.write,使用expires_in来设置过期时间,不设置的话默认不过期。

    1
    Rails.cache.write(cache_key, 10 ,expires_in: 10.seconds)
  • 读取Cache, 使用Rails.cache.read,传入key,读取value

    1
    Rails.cache.read(cache_key)
  • 获取Cache对象,使用Rails.cache.send

    1
    Rails.cache.send(:read_entry, cache_key,{})
  • 增加Cache中integer类型的值, 使用Rails.cache.increment, 注意这边如果不设置expires_in,则该条目就不再过期,无论原先记录write时有没有设置过期时间。

    1
    Rails.cache.increment(cache_key,1, expires_in: 10.seconds)
  • 读取Cache,如果没有就获取内容后插入, 使用Rails.cache.fetch

    • fetch不加block,就相当于read
    • fetch加了block,那么如果要获取的cache不存在,就会将block中的值添加到cache中,并返回。
      1
      2
      3
      Rails.cache.fetch('test_key') do
      'hello world'
      end

上述几个命令操作过程的记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2.3.8 :135 > cache_key='test_key'
=> "test_key"
2.3.8 :136 > Rails.cache.write(cache_key, 10 ,expires_in: 10.seconds)
=> true
2.3.8 :137 > Rails.cache.send(:read_entry, cache_key,{})
=> #<ActiveSupport::Cache::Entry:0x00007fbfa8151750 @value=10, @created_at=1603420143.660662, @expires_in=10.0>
2.3.8 :138 > Rails.cache.read(cache_key)
=> 10
2.3.8 :139 > Rails.cache.increment(cache_key,1, expires_in: 10.seconds)
=> 11
2.3.8 :140 > Rails.cache.read(cache_key)
=> 11
2.3.8 :141 > Rails.cache.send(:read_entry, cache_key,{})
=> #<ActiveSupport::Cache::Entry:0x00007fbfa9003f78 @value=11, @created_at=1603420143.666131, @expires_in=10.0>
2.3.8 :142 >

Reference


输出请求中所有header的值

在base controller中添加如下代码输出headers值。

1
2
3
4
5
6
7
8
9
10
11
12
before_action :output_headers
def output_headers
headers = request.headers.env.select do |k, _|
k.downcase.start_with?('http') ||
k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES)
end
headers = headers.sort.to_h
headers.each do |k,v|
Rails.logger.info "#{k} = #{v}"
end
end

Ruby中Hash根据key值进行排序

1
Hash.new.sort.to_h

Ruby中将Html转为plain text的方法

方法一: Gem html_to_plain_text

  • 地址: html_to_plain_text
  • 用法:
    1
    2
    3
    4
    require 'html_to_plain_text'
    html = "<h1>Hello</h1><p>world!</p>"
    HtmlToPlainText.plain_text(html)
    => "Hello\n\nworld!"

方法二: Rails的strip_tags函数

  • 地址: strip_tags
  • 用法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    include ActionView::Helpers::SanitizeHelper
    strip_tags("Strip <i>these</i> tags!")
    # => Strip these tags!
    strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
    # => Bold no more! See more here...
    strip_tags("<div id='top-bar'>Welcome to my website!</div>")
    # => Welcome to my website!
    strip_tags("> A quote from Smith & Wesson")
    # => > A quote from Smith & Wesson

Ruby中获取类的父类的途径

类的.ancestors方法,可以获取父类的列表。

1
2
3
4
5
6
2.7.1 :014 > Integer.ancestors
=> [ActiveSupport::NumericWithFormat, ActiveSupport::ToJsonWithActiveSupportEncoder, Integer, Mongoid::Criteria::Queryable::Extensions::Numeric, Mongoid::Extensions::Integer, BSON::Integer, JSON::Ext::Generator::GeneratorMethods::Integer, MessagePack::CoreExt, Numeric, Comparable, ActiveSupport::Dependencies::ZeitwerkIntegration::RequireDependency, ActiveSupport::ToJsonWithActiveSupportEncoder, Object, PP::ObjectMixin, Mongoid::Criteria::Queryable::Extensions::Object, Mongoid::Extensions::Object, BSON::Object, JSON::Ext::Generator::GeneratorMethods::Object, ActiveSupport::Tryable, ActiveSupport::Dependencies::Loadable, Kernel, BasicObject]
2.7.1 :015 >
2.7.1 :016 > 1.class.ancestors
=> [ActiveSupport::NumericWithFormat, ActiveSupport::ToJsonWithActiveSupportEncoder, Integer, Mongoid::Criteria::Queryable::Extensions::Numeric, Mongoid::Extensions::Integer, BSON::Integer, JSON::Ext::Generator::GeneratorMethods::Integer, MessagePack::CoreExt, Numeric, Comparable, ActiveSupport::Dependencies::ZeitwerkIntegration::RequireDependency, ActiveSupport::ToJsonWithActiveSupportEncoder, Object, PP::ObjectMixin, Mongoid::Criteria::Queryable::Extensions::Object, Mongoid::Extensions::Object, BSON::Object, JSON::Ext::Generator::GeneratorMethods::Object, ActiveSupport::Tryable, ActiveSupport::Dependencies::Loadable, Kernel, BasicObject]
2.7.1 :017 >


Ruby产生随机数

使用randRandom.rand来生成随机数

1
2
3
4
5
6
7
8
9
10
11
2.7.1 :026 > rand(1..10)
=> 10
2.7.1 :027 > rand(1.1..99.99)
=> 22.250780345080248
2.7.1 :028 >
2.7.1 :029 >
2.7.1 :030 > Random.rand(1...10)
=> 1
2.7.1 :031 > Random.rand(1.0...10)
=> 7.646093836025024
2.7.1 :032 >


产生测试数据的Gem Faker

faker-ruby/faker这个Gem,可方便地用于生成测试数据。

用法

Gemfile中 添加在development组里

1
2
3
4
5
group :development do
...
gem 'faker'
...
end


常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 生成随机图片
image_size = "#{rand(400...500)}x#{rand(500...600)}"
Faker::LoremFlickr.image(size: image_size, search_terms: ['sports'])
# 生成随机email
Faker::Internet.email
# 生成标题
title = [Faker::Company.name, Faker::Company.industry].join(' - ')
# 生成描述文字
desc = Faker::Quote.matz
desc = Faker::Hipster.sentences.sample
# 生成随机时间
time = Faker::Time.between(from: DateTime.now - 365, to: DateTime.now + 365, format: :default)

从数组中随机挑选出一个元素

使用Array.sample方法实现随机挑选的操作。

1
2
3
4
5
6
7
2.7.1 :047 > (1..100).to_a.sample
=> 81
2.7.1 :048 > (1..100).to_a.sample
=> 13
2.7.1 :049 > (1..100).to_a.sample
=> 91
2.7.1 :050 >


Rails中转换为Datetime

从字符中得到Datetime: '2020-01-01 00:00:00'.to_datetime
从timestamp得到Datetime: Time.at(1).to_datetime


Rails中validate添加自定义错误

使用.errors.add :base, string来添加自定义的错误。
例如: 实现unique validate的自定义错误。

1
2
3
4
5
6
validate do |block_key_word|
existing_record = BlockKeyWord.where(:name => block_key_word.name).first
unless existing_record.nil? || existing_record.id == block_key_word.id
block_key_word.errors.add :base, ("关键字-#{block_key_word.name}已存在。")
end
end

ruby操作Excel的基本操作

可以使用rubyXL来操作excel
写Excel的例子

1
2
3
4
require 'rubyXL'
workbook = RubyXL::Workbook.new
workbook[0].add_cell(0, 0, "写入内容")
workbook.write("#{Rails.root}/tmp/file.xlsx")


将html中的相对路径转为绝对路径的方法

使用URI.join来转换为绝对路径。URI.join(url, image_relative_path).to_s
如:

1
URI.join('http://www.81.cn/jmywyl/2020-04/04/content_9784967.htm?from=singlemessage&isappinstalled=0','../../attachement/jpg/site351/20200404/309c236f8da41ff2b3033e.jpg').to_s

生成结果:=> "http://www.81.cn/jmywyl/attachement/jpg/site351/20200404/309c236f8da41ff2b3033e.jpg"


假删除

acts_as_paranoid是用于model中假删除的一个Gem,使用deleted_at字段是否有值来判断数据是否删除。

acts_as_paranoid混合uniqueness的用法, 使得不删除的物料只能唯一存在。

1
validates :name, :uniqueness => { :message => "名字不能重复。" , :scope => :deleted_at}


Rails development模式下同时收到请求卡住的解决办法

Rails 5.1.7中,在development模式下,如果前端同时发请求特别快,可能会导致后端rails server直接挂起(hang)。
解决方法:
config/environments/development.rb中设置config.eager_load = true

如果还不能解决,尝试将puma的线程池数量改为1
config/puma.rb中设置threads_count = ENV.fetch("RAILS_MAX_THREADS") { 1 }

Reference:


DateTime.parse和Time.parse 默认时区不同

DateTime.parse默认使用的是UTC时区parse,Time.parse默认使用的是系统时区
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
2.3.8 :060 > Time.zone
=> #<ActiveSupport::TimeZone:0x00007fe7d4fa9330 @name="Beijing", @utc_offset=nil, @tzinfo=#<TZInfo::DataTimezone: Asia/Shanghai>>
2.3.8 :061 >
2.3.8 :062 > date_string="2021-01-01 00:00:00"
=> "2021-01-01 00:00:00"
2.3.8 :063 >
2.3.8 :064 > Time.parse(date_string)
=> 2021-01-01 00:00:00 +0800
2.3.8 :065 >
2.3.8 :066 > DateTime.parse(date_string)
=> Fri, 01 Jan 2021 00:00:00 +0000
2.3.8 :067 >
2.3.8 :068 >

参考: Rails parse date time string in UTC zone


Rails中文字符按byte截取

有些情况下,需要按照byte长度来截取中文,但是中文字符是多字节的,直接按照子节长度截取可能会截取不完全。
此时,使用ActiveSupport::Multibyte::Chars中的limit方法,就可以解决这个问题。

用法: "你好,世界!".mb_chars.limit(12).to_s, 输出: "你好,世"

rails c实验如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2.3.8 :073 > str="你好,世界!"
=> "你好,世界!"
2.3.8 :074 > str.mb_chars
=> #<ActiveSupport::Multibyte::Chars:0x00007f87d3f5a548 @wrapped_string="你好,世界!">
2.3.8 :075 > str.mb_chars.bytes
=> [228, 189, 160, 229, 165, 189, 239, 188, 140, 228, 184, 150, 231, 149, 140, 239, 188, 129]
2.3.8 :076 > str.mb_chars.bytesize
=> 18
2.3.8 :077 > str.mb_chars.limit(5).to_s
=> "你"
2.3.8 :078 > str.mb_chars.limit(6).to_s
=> "你好"
2.3.8 :079 > str.mb_chars.limit(7).to_s
=> "你好"
2.3.8 :080 > str.mb_chars.limit(8).to_s
=> "你好"
2.3.8 :081 > str.mb_chars.limit(9).to_s
=> "你好,"
2.3.8 :082 > str.mb_chars.limit(10).to_s
=> "你好,"
2.3.8 :083 >

Reference: Ruby: Limiting a UTF-8 string by byte-length


Rails中使用AWS S3 object presign url

Gemfile中

1
gem 'aws-sdk', '~> 3'

签名方法:

1
2
3
4
5
bucket="target-bucket-name"
object_name="obj.jpg"
obj = Aws::S3::Object.new(bucket, object_name)
obj.presigned_url(:put, acl: 'public-read')

接着拿获取到的签名的链接,在前端进行上传。

Reference: Class: Aws::S3::Object


rvm生成.ruby-gemset和.ruby-version

例如,以ruby 2.7.1中的rails6guide gemset为例, 使用如下命令

1
rvm --create --ruby-version ruby-2.7.1@rails6guide

会生成如下.ruby-version和

1
2
3
4
5
6
$ cat .ruby-version
ruby-2.7.1
$
$ cat .ruby-gemset
rails6guide
$


Ruby map, each, collect, inject, reject,select 简单用法

map

map是对array中的每一个元素执行action, 原始的array不会被修改,返回的是修改后的array

1
2
[1,2,3,4,5,6,7,8,9,10].map{|e| e*3 }
# returns [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]

each

each 只是对array中的每个元素执行action, 返回的还是原始的array

1
2
3
[1,2,3,4,5,6,7,8,9,10].each{|e| print e.to_s+"!" }
# prints "1!2!3!4!5!6!7!8!9!10!"
# returns [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

collect

map的别名

inject

Takes an accumulator (sum) and changes it as many times as there are elements in the array. Returns the final value of the accumulator.

1
2
[1,2,3,4,5,6,7,8,9,10].inject{|sum,e| sum += e }
# returns 55

You can also specify an initial value as parameter before the block.

1
2
["bar","baz","quux"].inject("foo") {|acc,elem| acc + "!!" + elem }
# returns "foo!!bar!!baz!!quux"

reduce

inject的别名

select

类似其他语言的filter,对array中的每个元素执行一个运算,如果结果是true,则对应的元素将会被添加到返回的数组中

1
2
[1,2,3,4,5,6,7,8,9,10].select{|el| el%2 == 0 }
# returns [2,4,6,8,10]

find

对数组中的元素执行运算,返回第一个true的元素。

1
2
[1,2,3,4,5,6,7,8,9,10].find{|el| el / 2 == 2 }
# returns 4

detect

find的别名

reject

select的作用相反,对array中的每个元素执行一个运算,如果结果是false, 则对应的元素会被添加到返回的数组中

1
2
[1,2,3,4,5,6,7,8,9,10].reject{|e| e==2 || e==8 }
# returns [1, 3, 4, 5, 6, 7, 9, 10]

Reference


字符串去除空格,\t,\r,\n

使用String类的delete方法, 如

1
string.delete(" \t\r\n")


去除所有html标签的方法

使用include ActionView::Helpers::SanitizeHelper中的strip_tags方法

1
2
include ActionView::Helpers::SanitizeHelper
strip_tags(html_string)

字符串转正则的方法

使用方法regex = Regexp.new regex_string。例如:

1
2
3
4
5
6
7
8
9
src_string="I'm a boy"
regex_string ="[Bb]oy"
regex = Regexp.new regex_string
if src_string =~ regex
print "Match"
else
print "MissMatch"
end

RestClient response中获取request.url

有些网页请求,会有redirecct,导致获取数据的网页和直接请求的Url不同。
如果用的是RestClient, 可使用response.request.url来获取redirect后的url。


RestClient 发送请求只获取headers的方法

只获取网络上图片和视频大小时,可以只获取url的header,然后根据content_length来获取对应图片或视频的大小,而不用将整个文件下载下来。
RestClient发送请求只获取header的方法是,使用:head的method。

1
response = RestClient::Request.execute(method: :head, url: url, timeout: 10)

注意点: 因为只获取header,因此response中的body是空的。因此就不能用response.present?来判断请求是否成功。

例子:

1
2
3
4
url="http://lh.rdcpix.com/00b95cd7bb26ff97392d3fa5c480706cl-f61050563r.jpg"
response = RestClient::Request.execute(method: :head, url: url, timeout: 10)
puts response.headers
=> {:content_type=>"image/jpeg", :content_length=>"342576", :connection=>"keep-alive", :date=>"Thu, 25 Aug 2022 07:29:21 GMT", :last_modified=>"Thu, 26 May 2022 14:15:17 GMT", :etag=>"\"f0f01cc048b5b969abb45b5b3ad951af\"", :accept_ranges=>"bytes", :server=>"AmazonS3", :x_cache=>"Hit from cloudfront", :via=>"1.1 a3c2566f9e36ad3cdf79fc6307fcf566.cloudfront.net (CloudFront)", :x_amz_cf_pop=>"FRA53-C1", :x_amz_cf_id=>"PhcWnxq2z3HtZXXJIMYxltr7-ua08teFX7Ec-3sVJvzYTITCMjXgdQ==", :age=>"32481"}


在Array或者ActiveRecord::Relation中手动剔除不需要的数据的方法

有时候,数据查询时没法使用sql来剔除数据,需要捞出来后手动处理。
此时可遍历后再使用.select!方法选出符合要求的数据。
如下是一个三层的例子: items has_many sub_items, sub_items has_many item_children

1
2
3
4
5
6
items.each do |item|
item.sub_items.select! {|sub_item| sub_item.is_deleted == false}
item.sub_items.each do |sub_item|
sub_item.item_children.select! {|item_child| item_child.is_deleted == false}
end
end

API接口返回json格式时

Model.as_json方法,只有有限的:only,:except,:methods:include几个有限的方法,不建议使用。
使用Jbuilder来处理,会更加的方便快捷。


Rails设计API接口支持字段排序sort

代码摘录自Github gist mamantoha/experience.rb

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
30
31
32
# app/controllers/concerns/orderable.rb
module Orderable
extend ActiveSupport::Concern
module ClassMethods
end
# A list of the param names that can be used for ordering the model list
def ordering_params(params)
# For example it retrieves a list of experiences in descending order of price.
# Within a specific price, older experiences are ordered first
#
# GET /api/v1/experiences?sort=-price,created_at
# ordering_params(params) # => { price: :desc, created_at: :asc }
# Experience.order(price: :desc, created_at: :asc)
#
ordering = {}
if params[:sort]
sort_order = { '+' => :asc, '-' => :desc }
sorted_params = params[:sort].split(',')
sorted_params.each do |attr|
sort_sign = (attr =~ /\A[+-]/) ? attr.slice!(0) : '+'
model = controller_name.classify.constantize
if model.attribute_names.include?(attr)
ordering[attr] = sort_order[sort_sign]
end
end
end
return ordering
end
end

用法:

1
2
3
4
5
6
7
8
9
10
# GET /api/v1/experiences
def index
@experiences = apply_scopes(Experience).
order(ordering_params(params)).
# fix N+1 query problem
includes(:city, :user, :categories).
all
render json: @experiences
end

改造了下,用于自定义白名单valid_sort_keys, 并且低版本Rails, 比如Rails 3.x不支持Model.order({id: :desc})这种写法,因此需要拼order的字串才能使用。

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
30
31
32
33
## 排序设置
# 按发布时间排序, 按ID排序, 比如-release_time,+id , -表示倒序,+表示正序
sort_by = params[:sort]
## 例子
# # GET /api/v1/experiences?sort=-price,created_at
# # ordering_params(params) # => { price: :desc, created_at: :asc }
# # Experience.order(price: :desc, created_at: :asc)
ordering = {}
if sort_by.present?
sort_order = { '+' => :asc, '-' => :desc }
valid_sort_keys = ["id", "release_time"]
sorted_params = sort_by.split(',')
sorted_params.each do |attr|
sort_sign = (attr =~ /\A[+-]/) ? attr.slice!(0) : '+'
if valid_sort_keys.include?(attr)
ordering[attr] = sort_order[sort_sign]
end
end
else
ordering = {"id": "desc"}
end
ordering_string = ""
ordering.each do |k,v|
if ordering_string.blank?
ordering_string += "#{k.to_s} #{v.to_s}"
else
ordering_string += ",#{k.to_s} #{v.to_s}"
end
end
ordering_string = "id desc" if ordering_string.blank?


Rails获取当前数据库连接

rails c中输入如下指令,输出当前使用的数据库信息:

1
Rails.configuration.database_configuration[Rails.env]


Ruby中随机打乱Array的数据

使用Array的shuffle方法, 可随机打乱Array中的元素顺序

1
2
3
4
5
6
7
8
$ irb
2.3.8 :001 > [1,2,3,4].shuffle
=> [1, 4, 2, 3]
2.3.8 :002 > [1,2,3,4].shuffle
=> [1, 3, 2, 4]
2.3.8 :003 > [1,2,3,4].shuffle
=> [2, 3, 1, 4]
2.3.8 :004 >

Reference:


rvm 创建并使用新的gemset

开发中,可以使用rvm为每个项目创建各自的gemset,从而达到各个项目环境各自独立的目的。
ruby-2.3.0中创建名为test-for-blog的gemset为例,创建的步骤如下:

1
2
3
4
5
6
7
8
9
10
11
12
# 使用ruby版本2.3.0
rvm use ruby-2.3.0
# 创建名为test-for-blog的gemset
rvm gemset create test-for-blog
# 切换当前环境为该gemset
rvm use ruby-2.3.0@rails5
# 在该gemset中进行bundle安装gem
bundle install


获取两个日期中大的或小的值

可以将两个日期组成Array, 然后直接使用Array的max和min方法

1
2
3
4
5
[1] pry(main)> [DateTime.parse('2022-01-01 12:00:00'), DateTime.parse('2022-02-01 00:00:00')].max
=> Tue, 01 Feb 2022 00:00:00 +0000
[2] pry(main)> [DateTime.parse('2022-01-01 12:00:00'), DateTime.parse('2022-02-01 00:00:00')].min
=> Sat, 01 Jan 2022 12:00:00 +0000
[3] pry(main)>


将元素插入数组中的方法

按照位置进行插入

Array.insert(index, element)可以直接将element插入array中的第index的位置

1
2
3
[8] pry(main)> [1,2,3,4].insert(1,100)
=> [1, 100, 2, 3, 4]
[9] pry(main)>

插到数组末尾

Array.push(element) 或者 Array.insert(Array.length, element)

根据数组中某个元素的值来确定插入地址

  1. 使用index来获取符合条件的元素的索引值,以元素是hash为例:array.index {|h| h[:id] == 34 }
  2. 获取索引值后,再按照实际要求使用Array.insert(index_xx, element)来插入element

参考:


Array数组移除重复项

Array.uniq()可以移除Array中的重复项。
当指定block的时候,就按照block中返回的值来去重

1
2
3
4
5
6
7
8
> b = [["student","sam"], ["student","george"], ["teacher","matz"]]
=> [["student", "sam"], ["student", "george"], ["teacher", "matz"]]
> b.uniq { |s| s.first }
=> [["student", "sam"], ["teacher", "matz"]]
>
> b.uniq { |s| true }
=> [["student", "sam"]]
>

一些高阶用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## 根据元素某个函数的值来uniq
fruits = %w(orange apple banana)
fruits.uniq(&:size) => 结果为: ["orange", "apple"]
## 根据元素的类类型来uniq
objects = [1, 2, "a", "b", :c, :d]
objects.uniq(&:class) => 结果为: [1, "a", :c]
## 混合字段来uniq
User类有
- age
- name
- country
三个元素
[david, petter, raphael].uniq { |person| [person.age, person.country] }
上面式子会根据age,country组合来进行uniq

Reference:


从Url中获取文件后缀

方法:

1
2
file = 'http://recyclewearfashion.com/stylesheets/page_css/page_css_4f308c6b1c83bb62e600001d.css?1343074150'
File.extname(URI.parse(file).path) # => '.css'

但要注意URI.parse解析不正确的URL时,会报exception,注意根据实际情况进行catch

参考: How to get the file extension from a url?


替换Url中的host

方法: 使用URI.parse(url).host获取host,然后再string.gsub进行替换。

1
2
3
4
url="https://www.baidu.com/helloworld"
host = URI.parse(url).host rescue ''
url = url.gsub(host, "www.qq.com") if host.present?
## url => https://www.qq.com/helloworld

注意URI.parse解析不正确的URL时,会报exception,所以这边有rescue ''来catch exception。


字符串多分隔符分割

可在String.split中使用正则来实现

1
2
3
word = "Now is the,time for'all good people"
word.split(/[\s,']/)
=> ["Now", "is", "the", "time", "for", "all", "good", "people"]

回车换行分割的例子: word.split(/[\r\n]/)

参考:

Rails中按照自定义顺序选取数据

Mysql中可使用field()函数来自定义排序,PG中没有现成函数可用,可是使用order by CASE WHEN field='value1 THEN 1 WHEN field='value2 THEN 2 ... END的方式来使用。下面介绍在Rails中如何使用

Rails 7 中现成的ActiveRecord::QueryMethods#in_order_of 函数

参照Github中的Pull Request Add ActiveRecord::QueryMethods#in_order_of, Rails 7中有in_order_of方法来实现上述功能。

Pull Request中有详细的使用方法介绍,摘录如下:

1
Post.in_order_of(:id, [3, 5, 1])

will generate the SQL:

1
SELECT "posts".* FROM "posts" ORDER BY CASE "posts"."id" WHEN 3 THEN 1 WHEN 5 THEN 2 WHEN 1 THEN 3 ELSE 4 END ASC

However, because this functionality is built into MySQL in the form of theFIELD function, that connection adapter will generate the following SQL instead:

1
SELECT "posts".* FROM "posts" ORDER BY FIELD("posts"."id", 1, 5, 3) DESC

Rails 7 以下的MySQL数据库使用方法

sort in MySQL:

1
2
3
> ids = [2,5,7]
=> [2, 5, 7]
> Image.where(id: ids).order("field(id, #{ids.join(',')})")

生成的SQL为:

1
SELECT `images`.* FROM `images` WHERE `images`.`id` IN (2, 5, 7) ORDER BY field(id, 2,5,7)

MySQL下的field()函数对不在field()函数中的数据的排序还有一些说明,可以参考 PostgreSQL TipsPG中如何实现类似MySQL中按照FIELD函数排序的功能这一章节的说明。

Rails 6.1以下的PG数据库使用方法

参考自: https://stackoverflow.com/a/26777669, 需要在model中内建self.order_by_ids方法。

1
2
3
4
5
6
7
8
9
10
11
def self.order_by_ids(ids)
order_by = ["CASE"]
ids.each_with_index do |id, index|
order_by << "WHEN id='#{id}' THEN #{index}"
end
order_by << "END"
order(order_by.join(" "))
end
User.where(id: [9,1000,2]).order_by_ids([9,1000,2]).map(&:id)
#=> [9, 1000, 2]

上面model查询对应生成的语句为:

1
SELECT "users".* FROM "users" WHERE "users"."id" IN (9, 1000, 2) AND ("users".deleted_at IS NULL) ORDER BY CASE WHEN id='9' THEN 0 WHEN id='1000' THEN 1 WHEN id='2' THEN 2 END

Rails 6.1以上的PG数据库使用方法

参考自: https://stackoverflow.com/a/66517571

Rails 6.1以上直接进行sql拼装的话,会报ActiveRecord::UnknownAttributeReference的错误。

1
ActiveRecord::UnknownAttributeReference (Query method called with non-attribute argument(s): "CASE WHEN id='9' THEN 0 WHEN id='1000' THEN 1 WHEN id='2' THEN 2 END")

所以self.order_by_ids的方法应该修改为如下:

1
2
3
4
5
6
7
8
def self.order_by_ids(ids)
t = User.arel_table
condition = Arel::Nodes::Case.new(t[:id])
ids.each_with_index do |id, index|
condition.when(id).then(index)
end
order(condition)
end

此时User.where(id: [9,1000,2]).order_by_ids([9,1000,2])生成的sql如下, 可以正常work。

1
SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."id" IN (9, 1000, 2) ORDER BY CASE "users"."id" WHEN 9 THEN 0 WHEN 1000 THEN 1 WHEN 2 THEN 2 END

Rails中使用PG中ARRAY_POSITION函数来实现

参考自: https://stackoverflow.com/a/57798183, 在PG中,可以在order语句中使用ARRAY_POSITION来实现自定义排序。

1
2
ids=[9,1000,2]
User.where(:id => ids).order("ARRAY_POSITION(ARRAY#{ids.to_s}::int[], id)").pluck(:id)

生成的SQL语句如下,也可以实现对应的功能。

1
SELECT "users"."id" FROM "users" WHERE 1=0 AND ("users".deleted_at IS NULL) ORDER BY ARRAY_POSITION(ARRAY[]::int[], id)

参考


遍历修改hash中的值

遍历修改hash中值的方法

1
2
3
4
5
6
7
my_hash = {"a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5}
my_hash.each do |k,v|
my_hash[k] = v.ordinalize
end
puts my_hash

结果为: {"a"=>"1st", "b"=>"2nd", "c"=>"3rd", "d"=>"4th", "e"=>"5th"}

其他修改方法可参考: Changing every value in a hash in Ruby

阅读此文

2020-05-04
Aliyun Linux 2上imageMagick的fonts配置问题

现象

公司阿里云服务器部署Rails程序时,发现Gem simple_captcha2生成的验证码图片,和以前的不太一样。图片中的数字会比正常情况下靠上很多, 影响用户体验。

正常情况下的图片:
fonts_normal.jpeg

有问题的图片:
crooked_image.jpeg

原因

Aliyun Linux 2(也包含最新的CentOS 7)中升级了ImageMagick的版本到ImageMagick 6.9.10-68, 字体配置文件/etc/ImageMagick-6/type.xml中默认改用了urw-base35-fonts,而不是之前的ghostscript-fonts

解决方法

安装并修改ImageMagick使用旧字体。

1
2
3
4
5
6
7
8
9
# 安装urw-base35-fonts-legacy, fonts在/usr/share/X11/fonts/urw-fonts目录下
yum install urw-base35-fonts-legacy
# 修改/etc/ImageMagick-6/type.xml配置文件为使用type-ghostscript.xml
sed -i.bak 's,type-urw-base35.xml,type-ghostscript.xml,g' /etc/ImageMagick-6/type.xml
# 修改 /etc/ImageMagick-6/type-ghostscript.xml为/usr/share/X11/fonts/urw-fonts/
sed -i.bak 's,metrics=",metrics="/usr/share/X11/fonts/urw-fonts/,g' /etc/ImageMagick-6/type-ghostscript.xml
sed -i 's,glyphs=",glyphs="/usr/share/X11/fonts/urw-fonts/,g' /etc/ImageMagick-6/type-ghostscript.xml
阅读此文