Ruby基本语法

笔记

Posted by Jingming on November 26, 2020

一、作为脚本语言(区别于面向对象语言)

  • 任何东西都是对象,”123”.object_id 返回”123”对象的hash地址,且每次返回地址不一样,因为每次输入的”123”

都被解析器当作新的对象。那如何让解析器知道是在引用之前的变量呢?使用变量符号,:var。

另外可以使用.class来查看类型。

  • 方法中如果没有显示return,那么最后一条语句的值;调用方法时参数不需要使用括号。

  • Ruby强制规定类中的setter方法名必须以=结尾。 Ruby对类实例变量进行赋值的时候,o.p = v,将其解释为 o.send(:p=, v),此处可以看出’p=’是一个 方法名。
  • 使用module,module和class的区别是不能生成实例,但是可以通过被include的方式来实现 代码复用。module相当于namespace。
  • 作为脚本语言,有命令行交互模式,按irb进入。
  • Ruby有代码块block的概念 block的定义:包含在花括号内,或者do end里面的代码就是block代码块。 block的作用:在函数内调用,做回调效果(类似面向对象里面的匿名函数)。 block的使用:首先,在函数中使用yield关键字作为占位符,就可以调用传入函数的block代码。 其次,有两种方式传入block:一种是隐式的,这种情况下,函数定义时候没有什么特别的,但调用的时候,可以在后面接代码块(最常见的就是接一个do end); 还有一种就是显式的,在函数定义的时候使用&block作为函数参数,函数执行时候会把block转化成一个Proc对象(名字叫block),支持使用block.call()来调用代码块,函数调用的时候,也是直接在后面接代码块。 第二种方式好像显得多此一举,实际不然。显示的定义block的好处是主要有几个: 第一:有Proc对象意味着我们知道了传入函数的block代码块的引用,这样就可以把它继续传给函数内部的也要复用这个block的其他函数; 第二:有Proc对象意味着可以动态加载执行,而不是(像inline一样)直接被静态加载到函数里面立即执行了,使用block代码块中有比较复杂的代码的情形。 第三:block.call()其实是可以传入参数的(定义block的时候可以使用||定义形参)。

  • 使用单引号的字符串表示可以包含”格式化”方式的字串(也称可以eval的字符串);双引号的字符串则是保持原样的字符串。
  • self修饰的方法是类方法。
  • initialize 方法相当于Java的构造函数。
  • Rails作为Ruby的Web框架,相比于Java的框架,对数据库更加的依赖,任何环境,包括开发环境,都必须 创建数据库才能运行。
  • require相当于Java的import。

二、特殊符号

| 符号 | 实例 | 实例解释 | 注意事项 | | ——– |:—–:| :—–:| :—–:| | ! | foo.sort! | 表示不是对foo对象的深拷贝进行修改,而是对foo对象做修改 | | : | :var | Symbol变量,系统运行时候始终保留该变量的引用,也可以理解为全局变量,注意:var是变量名,而不是”:“做修饰、var做变量名。| 为了做到全局性,Ruby会在程序运行期间维持一个Symbol变量表,任何类C,方法M,都会默认有:C, :M变量在Symbol表里面,可以使用Symbol.all_symbols来查看,同名的Symbol变量会被替换,Symbol也是一种类型,Symbol变量适合作为哈希的key | | : | fun(var:) | var:做参数,说明var是hash类型 |https://medium.datadriveninvestor.com/ruby-keyword-arguments-817ed243b4e2| | @ | 类中 @name | 加在类中变量name前面,有this的效果,表示变量是类的一个实例变量 | 类可以不定义变量name,在类方法中仍可以使用@name| | @@ | 类中 @@count | 加在类中变量count前面,表示count是属于类的,而不是属于类对象的 | 类必须显示定义变量count,使得count成为类变量| | # | #{变量名} | 放在字符串”“中,有格式化字符串%s的效果 | | attr_accessor :variable_name | attr_accessor :x, :y | 给类添加两个实例变量,且给两个变量默认的带上getter和setter。理解:accessor=reader+writer | 变量名前一定要带 : ,变量名之间要用 , 分割 | | $ | $var | 声明var是全局变量,一般写在函数和类的外面 || | || | [4, 6, 8, 13].find { |e| e > 7 } | 结果是8, find找的是第一个满足条件的值 | 结合循环使用,e表示当前迭代器访问的元素| | reduce(sym) → obj | (1..8).reduce(:+) | 结果是1到8的总和 | sym表示运算符号| | reduce(initial, sym) → obj | (1..8).reduce(100, :+) | 结果是1到8的总和 + 100|| | reduce(initial) { |memo, obj| block } → obj| (1..8).reduce(100) { |sum, num| sum += num } | 结果是1到8的总和 + 100| memo表示reduce循环时候的累计迭代器(accumulator), obj表示当前迭代器访问的元素, memo会被初始化为initial值,没有给定initial值时,会默认选择调用集合中的第一个元素作为memo值| | inject | [3, 6, 10].inject {|sum, number| sum + number} =>|3, 6| 3 + 6 => 9=>|9, 10| 9 + 10 =>19 | inject是reduce的同义词 | https://medium.com/@terrancekoar/inject-method-explained-ed531eff9af8 | | * | def sample (*test) | 可变参数,也就是说test是一个大小不确定的数组|| |_| for _ in range print (‘x’)| _表示一个不重要的临时变量 |select|[1,2,3,4,5,6].select(&:even?) => [2,4,6]; [1].select(&:even?) => []| 返回的是数组(或空数组)| |collect or map| array = [“a”, “b”, “c”] array.map { |string| string.upcase } => [“A”, “B”, “C”] | 作用是对数组遍历,并将每次遍历的计算的最后一条语句结果放入结果数组,最后返回结果数组|与使用each的重大区别就在于each并没有所谓的结果数组,而是每次都返回原始数组的修改结果(类型和原始数组完全一致,而map不一定)。 |class « | class « self | 参考http://wemee.blogspot.com/2014/07/ruby-class.html 总结:class « 可以修改对象的方法定义,self的时候,相当于修改类方法|| |方法参数中使用&|def print_phrase(&block) block.call end|https://medium.com/@sologoubalex/parameter-with-ampersand-operator-in-ruby-6a6a7fd666d5|可以将块作为参数传入函数| | proc | Proc.new{}|https://ruby-china.org/topics/10414| proc的作用是使得块实例化成proc对象以便在方法间传递,proc对象的好处是可以设置其中部分的变量,然后在其基础上调用(call方法) | |__FILE__|__FILE__|返回代码所在文件的项目路径|| |$PROGRAM_NAME or $0| $PROGRAM_NAME | 返回当前进程的命令所在路径 | | | fetch | map.fetch | map取值的手段,https://stackoverflow.com/questions/16569409/fetch-vs-when-working-with-hashes| | | ||= | x ||= y| 等价于 x || x = y, 意思是x不为空时候才使用y替换 | | | &: | array.map(&:method_name) | 意思是调用map里面的每个method_name方法,方法结果放入结果数组|| |Enumerable | class A include Enumerable| 意思是A类是Enumerable的(可数的),也就是可以被遍历,这种遍历实现原理是实现了Enumerable类会定义一个接口,里面提供集合(数组)类型的数据来遍历| https://dockyard.com/blog/2018/02/21/what-does-it-mean-to-be-enumerable 对于脚本语言、万物皆为对象的语言来说,类的属性其实也看作为一个对象,属性对象也有自己的属性(例如规定权限,包括可读可写遍历等,通过getOwnPropertyDescriptor来查看) | |<=>|https://stackoverflow.com/questions/26581619/rubys-operator-and-sort-method/28014514||| |Array.shift| | 从数组中移除一个元素并返回它|| |tap|对象.tap block|意思是对象先调用block,之后返回对象自己。作用是例如在block里写入puts语句,这样可以不影响链式调用的情况下调试对象;又例如在block里面使用方法build对象等|https://www.cnblogs.com/nbkhic/p/4233168.html https://ruby-china.org/topics/5348 https://medium.com/aviabird/ruby-tap-that-method-90c8a801fd6a| |take|enu.take(n)|取出enumerable对象的前n个数|如果是 ActiveRecord::FinderMethods里面的take,那么也是取出第一个元素,就是limit 1的意思| |params| params.require params.permit| params 看起来像hash,但是在controller里面,它其实是一个对象,类型为ActionController::Parameters,require和permit都是其中的方法 | require的意思是需要某个parameter的存在,不存在的话抛出异常,否则还是返回params本身;permit返回params的copy,但是只包括permititted keys| |reject|(1..30).reject{ |n| n % 2 == 0 }|[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]|去除数组中满足block的元素| |send|1.send(“methods”)|调用Integer中的methods方法列出Integer对象中所有方法|X.send(”Y”, arg1, arg2..)的作用是向对象X发送调用Y方法的请求,参数是arg1和arg2,至于X是否真的有Y方法,不知道,不过可以使用respond_to?先判断该对象是否真的有这个方法|https://medium.com/@pojotorshemi/send-me-a-river-ruby-send-method-3b295173e5c8| |super||调用最后一个(有该方法的)父类中的同名方法|| |[]|[a,b,c]||数组| |{}|{“a” => 1,”b” => 2, “c” => 3}]||哈希表|

三、具体使用实例

  • 数组元素遍历
aaa = []
[{:a => 1, :b => 2}, {:a => 3, :b => 4}].each {|h| aaa << { "p" => h[:a], "q" => h[:b]} }
aaa
  • 数组元素遍历修改 map 相对于 each 就是会将操作后的结果收集起来,并返回这个新数组。 ``` myarr = [{“a”=>3, “b”=>2}, {“c”=>3, “a”=>3}] puts myarr.map {|e| e[“a”] = 100 e[“c”] = 101 }

#output: [101, 101]

puts myarr

#output : [{“a”=>100, “b”=>2, “c”=>101}, {“c”=>101, “a”=>100}]


注意,如果使用myarr.map!,那么myarr本身将被修改为myarr.map!的结果,也就是每次循环最后一条语句值组成的数组,如下:

myarr = [{“a”=>3, “b”=>2}, {“c”=>3, “a”=>3}] puts myarr.map! {|e| e[“a”] = 100 e[“c”] = 101 }

#output: [101, 101]

puts myarr

#output : [101, 101]


array.map { |x| x == 4 ? 'Z' : x }

- 对象是否为空,不为空则调用其中的方法,或者调用不存在的(尚未实现的)方法

解决方法:(1)使用 对象名.try(:方法名, :方法参数),如果方法不存在,返回nil

举例: @Person.spouse.name if @Person && @Person.spouse <=> @Person.try(:spouse).try(:name)

(2)使用 对象名.try!(:方法名, :方法参数)

和try的区别是,在对象不为空,但是没有该方法的时候,try会返回nil而try!会抛出NoMethodError异常。

(3)调用不存在的(尚未实现的)方法,使用 对象名&.方法名

等价于对象名.try!(:方法名, :方法参数)

> https://apidock.com/rails/v3.2.1/Object/try <br/>
> https://devdocs.io/rails~5.1/object#method-i-try-21
> https://stackoverflow.com/questions/45825363/what-is-the-difference-between-try-and-safe-navigation-operator-in-ruby

- 处理嵌套的数组或哈希为空的

**解决方法**:使用dig。

例如,我们有params = { user: { choices: \["1", "3", "5"\] } }。

现在要读取里面的choices。使用params\[:user] && params\[:user]\[:choices] || \[],

注意其中在访问choices之前,要保证user不为空,否则返回空数组。

这样在嵌套内容多的时候,会写出一堆空判断,可以使用dig,写法是:params.dig(:user, :choices) || \[]。

这个好处不明显,我们再来看一个复杂点的例子,params\[:user] && params\[:user]\[:address] && params\[:user]\[:address]\[:street] && params\[:user]\[:address]\[:street]\[:number]

如果使用dig,那么可以写为params.dig(:user, :address, :street, :number)。

再来看看数组的dig用法:array.dig(0, 1, 1) <=> array\[0]\[1]\[1]

- select的next用法

(1..10).select do |a| next a if a.even? a.odd? end

=> [1,2,3,4,5,6,7,8,9,10]

```

理解:next a的意思是,if的返回结果true的话直接把a保留在结果里,然后循环返回move到下一个元素, 下一个元素必然是奇数,且奇数.odd?返回true,所以同样被select到最终结果里面。

四、Ruby文件组织(Rails框架)

  • 冻结文件中的字符串

https://ruby-china.org/topics/35215

  • 使用配置文件

使用Rails.application.config_for读取项目config目录下的yml文件。 yml文件中,支持ERB语法,说白了,就是使用标签<%= %>,这样里面可以使用Rails的复杂变量,例如ENV,是Rails 中定义的系统环境变量,来源是drakefile中的配置,可以配置path,也可以直接加入键值对。

https://www.justinweiss.com/articles/the-lesser-known-features-in-rails-4-dot-2/

  • 在Module 里面included

def self.included(xx) # 这里面的代码将在module被include时候执行,xx是include module的class的一个具体实例。 end

Ruby核心方法

  1. instance_exec:可以在对象实例的上下文中执行代码块,并可以传入参数给代码块。 instance_exec(*args) { |arg1, arg2, …| block }