发布于 

Ruby哈希中神奇的Symbol

ruby 在 1.9.x 版本开始支持使用json方式创建哈希(Hash),示例代码如下:

1
2
3
4
5
6
7
8
# 1
user1 = {"name": 'Jhon', "age": 21}

# 2
user2 = {name: 'Jhon', age: 21}

# 3
user3 = {:name => 'Jhon', :age => 21}

#1 和 #2 中 user 的 Key(name 和 age)都默认是 Symbol 类型,而 #3 的 user 已经指定了 Key 类型是 Symbol

而在 ruby 1.9.x 之前的版本不可以使用冒号:即类似#2方式语法创建,只能像下面这样创建 Hash:

1
2
3
user1 = {"name" => 'Jhon', "age" => 21}

user2 = {:name => 'Jhon', :age => 21}

可以看出 Symbol 一直都存在,并且在新版本得到了“重用”。

当然了,最新的 ruby 版本也支持 1.9.x 之前 Hash 创建方式:

1
user4 = {"name" => 'Jhon', "age" => 21}

这个创建 Hash 的方法,Key 是 String 类型而不是 Symbol 类型。

我现在用的是 ruby 2.7.x 版本。

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
# frozen_string_literal: true

user1 = { "name": 'Jhon', "age": 21 }
user1.each_key do |key|
puts "user1 key 类型:#{key.class.to_s}"
end

puts("--------------------天生我材必有用--------------------")

user2 = { name: 'Jhon', age: 21 }
user2.each_key do |key|
puts "user2 key 类型:#{key.class.to_s}"
end

puts("-------------------不以物喜不以己悲-------------------")


user3 = { :name => 'Jhon', :age => 21 }
user3.each_key do |key|
puts "user3 key 类型:#{key.class.to_s}"
end

puts("--------------------桃花依旧笑春风--------------------")


user4 = { "name" => 'Jhon', "age" => 21 }
user4.each_key do |key|
puts "user4 key 类型:#{key.class.to_s}"
end

运行得到结果

user1 key 类型:Symbol
user1 key 类型:Symbol
——————–天生我材必有用——————–
user2 key 类型:Symbol
user2 key 类型:Symbol
——————-不以物喜不以己悲——————-
user3 key 类型:Symbol
user3 key 类型:Symbol
——————–桃花依旧笑春风——————–
user4 key 类型:String
user4 key 类型:String

看到这里,你可能会很疑惑:写这么多,你到底是遇到了什么问题?!

我们先做好情绪管理,书归正传。

情景是这样的,我要从数据库(MySQL)user 表中读取数据,将读取的数据实例化为 ruby 的 User 类。这里我没有采用任何 ORM 工具,纯手工打造。

ruby user.rb 代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User
# 使用 attr_accessor 自动生成 getter 和 setter 方法
attr_accessor :id, :phone, :avatar, :age, :motto, :created_at, :updated_at

# 构造器,用于创建 User 对象时初始化字段
def initialize(attributes = {})
@id = attributes[:id]
@phone = attributes[:phone]
@avatar = attributes[:avatar]
@age = attributes[:age]
@motto = attributes[:motto]
@created_at = attributes[:created_at]
@updated_at = attributes[:updated_at]
end
end

User 类的属性和数据库表 user 的字段保持一致。

db.ruby 代码片段

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
# frozen_string_literal: true

require 'mysql2'
require_relative '../model/user'

begin
# 配置数据库连接
client = Mysql2::Client.new(
host: 'localhost',
username: 'root',
password: "",
database: "db"
)

# 查询数据
result = client.query("select * from user where id=1")
result.each do |row|
puts "query form table #{row}"
end

user_record = result.first

# 根据查询结果构建User类
user = User.new(user_record)

puts "user phone is: #{user.phone}"

ensure
# 关闭连接
client.close if client
end

结果有点出乎意料,实例化的 user 各个属性居然为空。百思不得骑jie……

开始调试,我用的是 RubyMine 进行开发的,会让你安装 ruby-debug-idedebase,直接安装就可以了。

真相就要大白了,我加个打印,各位看官就明白了。

1
2
3
4
user_record = result.first
user_record.each_key do |key|
puts "user_record key 类型: #{key.class.to_s}"
end

打印结果清一色是 String,而在 User 构造方法中的 Key 都是 Symbol 类型,明显类型不一致,所以实例化失败,导致 user 各个属性字段就为空了🤕。

解决方法很简单,从 MySQL 查询出来的 Hash 数据的 Key 类型(String)转换为 Symbol 即可,有两个常用的方法可以进行转换:

1
2
3
4
5
6
7
8
9
10
# 方法一:批量转换
user_hash = user_record.transform_keys(&:to_sym)

# 方法二:单独转换
user_hash = user_record.each_with_object({}) do |(column, value), hash|
hash[column.to_sym] = value
end

# 构建User类
user = User.new(user_hash)

至此,问题解决。


附上来自 AI 的解答:

使用 mysql2 查询 MySQL 数据库时,默认得到的哈希键是字符串类型,这是出于对不同数据库类型和查询结果灵活性的考虑。如果需要,您可以在处理查询结果时将这些字符串键转换为你需要的类型。


Ruby 是一门简单上手的编程语言,小巧可爱,有需要就去看看吧。顺便推一下自己的 Ruby on Rails 笔记:点此处直接飞往✈️


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

veryitman