Ruby 自检系列
本文基于 ruby-trivia 和 ruby-style完成,节选部分问题,并更进一步探究原理,用以加强自身对ruby基础的理解和应用。
Language Characteristics and Core Objects
Q: Is Ruby a statically typed or a dynamically typed language?
A: Dynamically typed since type checking is done at runtime.
Q: Is Ruby a strongly typed or a weakly typed language?
A: Strongly typed since an object's type is checked before an operation is performed on it.
Q: What is the difference between a statement and an expression in Ruby?
A: All statements are expressions in Ruby since all statements return a value.
(expressions > statements)
Data Types
Q: Does String
include the Enumerable
module?
A: No.
Q: What method might you use to enumerate over a string?
A: String#each_char
Hash
Q: How might you specify a default value for a hash?
A: Pass the default values as arguments to ::new on initialization or change the default directly with the method Hash#default. You may also provide a default at the time of query with Hash#fetch.
C: 在某些情况下使用Hash#fetch是比较合适的:
- 当处理应该存在的哈希键时,使用 Hash#fetch
heroes = { batman: 'Bruce Wayne', superman: 'Clark Kent' }
# 差 - 如果我们打错了哈希键,则难以发现这个错误
heroes[:batman] # => 'Bruce Wayne'
heroes[:supermann] # => nil
# 好 - fetch 会抛出 KeyError 使这个错误显而易见
heroes.fetch(:supermann)
- 当为哈希键的值提供默认值时,倾向使用 Hash#fetch 而不是自定义逻辑。
batman = { name: 'Bruce Wayne', is_evil: false }
# 差 - 如果仅仅使用 || 操作符,那么当值为假时,我们不会得到预期结果
batman[:is_evil] || true # => true
# 好 - fetch 在遇到假值时依然可以正确工作
batman.fetch(:is_evil, true) # => false
- 当提供默认值的求值代码具有副作用或开销较大时,倾向使用 Hash#fetch 的block形式
batman = { name: 'Bruce Wayne' }
# 差 - 此形式会立即求值,如果调用多次,可能会影响程序的性能
batman.fetch(:powers, obtain_batman_powers) # obtain_batman_powers 开销较大
# 好 - 此形式会惰性求值,只有抛出 KeyError 时,才会产生开销
batman.fetch(:powers) { obtain_batman_powers }
Q: Does Hash use #== or #eql? to compare hash keys?
A: #eql?
C: Why? What's the different between them?
Hash使用#eql?来做比较。而Numeric则是通过 #==做比较,而不是#eql?。在这种情况下,#eql?会做更严格的比较。
对于Object的对象, #eql? 是跟 #== 一样的。由此可知,Numeric是例外
equal?
则检查两者是否是同一对象(相同值,相同type)。
#对于 Numeric
x = 1
puts x.object_id # => 3
y = 1
puts y.object_id # => 3
z = 1.0
puts z.object_id # => -36028797018963966
x == y # true 同一个object
x.eql? y # true 同一个object
x == z #true 不同object,但是content相同
x.eql? z #false 不同object,并且是不同的type,x是Fixnum,y是Float
#对于 Array object
x = [1, 2, 3]
y = [3, 2, 1]
z = [1, 2, 3]
x.object_id # => 70244601475800
y.object_id # => 70244601492000
x.eql?(y) #=> false
x.object_id # => 70244601475800
z.object_id # => 70244601475800
x.eql?(z) #=> true
x.object_id # => 70244601475800
s = y.sort # => [1, 2, 3]
s.object_id # => 70244618664060
x.class # => Array
y.class # => Array
x == s #=> true 相同value
x.eql?(s) #=> true 相同value和type
x.equal?s #=> false 相同value和type,但是不是同一个对象
Q: What's is the precedence of &&, and, or, =, ||?
A: && > || > = > (and, or)
Q: .
vs ::
A: 调用类方法时,他们没有区别。但是使用::
可以访问constant和namespace
Q: !=
vs <=>
(spaceship)
A: !=
不等于。 <=>
,左边大于右边则返回1,等于返回0,小于返回-1
Q: What's true and false in ruby?
A: Everything in Ruby is true except false
and nil
.
TrueClass
, FalseClass
, but can't create a new instance for them. true
and false
are the only things we can get.
We can recognize them by either using nil?
or false
on ==
left-hand.
Q: What is duck type?
A: Walk like a duck, yip like a duck, it's a duck. In an other word, interface over type.
Q:
h = Hash.new
h[:s] = 'ok'
Can you retrive 'ok'
by h['s']
?
A: You cannot. Since the Hash class in Ruby’s core library retrieves values by doing a standard ==
comparison on the keys. This means that a value stored for a Symbol key (e.g. :my_value) cannot be retrieved using the equivalent String (e.g. ‘my_value’). On the other hand, HashWithIndifferentAccess treats Symbol keys and String keys as equivalent so that the following would work:
Q:#joins
vs #includes
in Rails ActiveRecord
A: #joins
performs an inner join between two tables.
orders = Order.joins(:listing)
=> SELECT "orders".* FROM "orders" INNER JOIN "listings" ON "listings"."id" = "orders"."listing_id"
内连接。对于 T1 中的每一行 R1 ,如果能在 T2 中找到一个或多个满足连接条件的行, 那么这些满足条件的每一行都在连接表中生成一行。In another word, It will retrieve all records where listing_id (of orders table) is equal to listing.id (listings table)
这时候并没有listing的数据被取出。所有如果要取某个order的关联关系listing的属性,还是回去查一次数据库:
order_1 = orders.first
//上个命令的执行结果被存在内存中了, 直接读取了。
=> #<Order id: 1, customer_id: 1, listing_id: 1, product_id: 1, created_at: "2016-04-08 02:30:16", updated_at: "2016-04-08 02:30:49", status: "paid">
order_1.listing.address
//但具体关联到某个order上的listing信息还在数据库中,需要进行一次查询。
=> SELECT "listings".* FROM "listings" WHERE "listings"."id" = $1 LIMIT 1 [["id", 1]]
#includes
performs a left outer like join
between the two tables.
orders = Order.includes(:listing)
=> SELECT "orders".* FROM "orders"
=> SELECT "listings".* FROM "listings" WHERE "listings"."id" IN (1, 2, 3, 4)
这时候执行的是类似于左外连接。
左外连接:首先执行一次内连接。然后为每一个 T1 中无法在 T2 中找到匹配的行生成一行, 该行中对应 T2 的列用 NULL 补齐。因此,生成的连接表里总是包含来自 T1 里的每一行至少一个副本。
为什么说是‘类似左外连接’?正常的左外连接应该是这样写的:
SELECT "orders".* FROM "orders" LEFT JOIN "listings" ON "listings"."id" = "orders"."listing_id";
但是在rails中我们并没有看到。这是因为在rails4中,我们需要手动写LEFT OUTER JOIN的sql查询语句:
orders = Order.joins('LEFT OUTER JOIN "listings" ON "listings"."id" = "orders"."listing_id"')
其实rails帮我们做的是先把所有orders表里的记录取出。然后再在listings表中取出可以跟orders表记录匹配的记录,并存在内存中。
好消息是rails5中加入了一个新方法#letf_outer_joins
来执行LEFT OUTER JOINS.
这时候我们再来执行一次查询某个order的关联关系listings上的属性:
order_1 = orders.first
//从内存中取
=> #<Order id: 1, customer_id: 1, listing_id: 1, product_id: 1, created_at: "2016-04-08 02:30:16", updated_at: "2016-04-08 02:30:49", status: "paid">
order_1.listing.address
//因为listings表中的相应记录已经在第一步中被取出,所以这时候直接从内存中读即可。
=> {"city"=>"Nantes (44000)", "street_number"=>"", "region"=>"Pays de la Loire", "street"=>""}
可见,再次查询的时候并不会去读数据库。