Module引入
类引入
ruby 中module用于替代多继承的功能,是一个可以包含常量、方法、类定义以及其他模块的集合。
module除了不可以new实例,以及没有Class类中一些特殊的方法和class没什么区别。
其定义语法如下:
module ModuleA
constA
def method1
...do something
end
endMix-in 可以将多个模块混合到类中,即通过include 在类中引入模块,以获得模块的方法、常量等。
class ClassA
include ModuleA
include ModuleB
...
end
class ClassB
include ModuleA
include ModuleB
...
end每个模块具有单独的命名空间,以保证模块下的常量和方法互不影响。
Const
在类中引入模块后,其中的常量都被引入到了所在类中,当然也可以直接通过模型的命名空间直接访问模型的常量。
module ModuleA
constA = 'a'
end
class ClassA
p ModuleA::constA # => "a"
include ModuleA # 引入模块
p constA # => "a" # 直接在类中使用模块中的常量
endMethod
在类中引入模块后,其中的方法可以被该类的实例调用。
同时也可以在模块中将方法定义为模块方法。
module ModuleA
def method1
p "do method1.."
end
def method2
p "do method2.."
end
module_function :method2 # 将方法2设置为模型方法
end
class ClassA
include ModuleA
# 可以直接通过模型的命名空间调用模型方法
ModuleA.method2 # => "do method2.."
a = ClassA.new
a.method1 # => "do method1.."
endNameSpace
在引入多个模块时如果常量出现同名问题可以指定具体的命名空间以使用正确的常量。
否则 ruby 将使用后引入的常量,且多次引入不会重新引入。
且在模块中的非模块方法(可以直接调用的,模块的自方法)同名时,引入模块的类实例没法精准指定具体方法,所以一定要注意。
module C
Name = 'lisi'
def hello(name)
p "#{Name} : hello," << name
end
# module_function :hello
end
module B
Name = 'zhangsan'
def self.hello(name)
p "#{Name} : hello," << name
end
# module_function :hello
end
class A
include C
include B
p Name # => "zhangsan"
B.hello "wangwu" # => "zhangsan : hello,wangwu"
# C.hello "wangwu" ## => 报NoMethodError (undefined method `hello' for C:Module)
a = A.new
a.hello "wangwu" # => "lisi : hello,wangwu" 因为在moduleB 中hello为自方法
endinclude & extend
include添加一个模块的方法到实例中extend添加一个模块的方法到类中
module Log
def class_type
"This class is of type: #{self.class}"
end
endclass TestClass
include Log
end
tc = TestClass.new.class_type
puts tc #This class is of type: TestClassclass TestClass
extend Log
# ...
end
tc = TestClass.class_type
puts tc # This class is of type: TestClass在ruby中使用include也可以增加实例方法,因为include有一个self.included的钩子函数,可以用它来修改类中对于模块的引入。
module Foo
def self.included(base)
base.extend(ClassMethods)
end
def foo
puts 'instance method'
end
module ClassMethods
def bar
puts 'class method'
end
end
end
class Baz
include Foo
end
Baz.bar # class method
Baz.new.foo # instance method
Baz.foo # NoMethodError: undefined method ‘foo’ for Baz:Class
Baz.new.bar # NoMethodError: undefined method ‘bar’ for #<Baz:0x1e3d4>extend也有一个叫self.extended的方法,作用和include中的self.included类似。
同时included方法可以用作文件夹在时的一些初始化操作:
module A
def A.included(mod)
puts "#{self} included in #{mod}"
end
end
module Enumerable
include A
end
# => prints "A included in Enumerable"尤其值得注意的是,self其实就是调用的该类的一个单例类,可以从下面的例子看到:
car = "car"
class << car
def f1; puts "f1"; end
def self.f2; puts "f2"; end
class << self
def f3; puts "f3"; end
def self.f4; puts "f4"; end
end
end
car.f1 # => f1
car.singleton_class.f2 # => f2
car.singleton_class.f3 # => f3
car.singleton_class.singleton_class.f4 # => f4module引入
不同于类,module本身不能实例对象,所以module通过extend引入别的module时,其中打方法会被添加到自身的metaclass中,并且可以通过module名直接调用:
module ExM
def hello_exm
puts 'hello exm'
end
end
module InM
def hello_inm
puts 'hello inm'
end
endmodule Base
include InM;
extend ExM;
end
Base.hello_exm
# => hello exm
Base.hello_inm
# => NoMethodError (undefined method `hello_inm' for Base:Module)而Base通过include引入到方法则会在Base被其他class通过include引入时添加到class到实例方法中:
class KLASS_IN
include Base;
end
KLASS_IN.hello_inm
# => NoMethodError (undefined method `hello_inm' for KLASS_IN:Class)
KLASS_IN.new.hello_inm
# => hello inm此时在KLASS_IN的self方法和实例的方法中都不能找到hello_exm方法:
KLASS_IN.hello_exm
# => NoMethodError (undefined method `hello_exm' for KLASS_IN:Class)
KLASS_IN.new.hello_exm
# => NoMethodError (undefined method `hello_exm' for #<KLASS_IN:0x00007fbebd098610>)可以先看一个例子:
module SelfM
def self.hello_self
puts "hello myself"
end
end
module Base
include SelfM;
extend SelfM;
end
Base.hello_self
# => NoMethodError (undefined method `hello_self' for Base:Module)可以看到在Base中没有找到SelfM中的方法。self的方法其实存放在metaclass中。而ruby中的引入不会将metaclass中的方法引入。
所以在上面的例子中ExM中的方法已经被添加到了Base的metaclass中,所以ExM中的方法在KLASS_IN的metaclass和实例中都找不到对应方法。
如果Base被extend到其他到类,同样ExM中的方法不会被引入到该类中,而InM中到方法会被添加到该类的metaclass中:
class KLASS_EX
extend Base;
end
KLASS_EX.hello_inm
# => hello inm
KLASS_EX.hello_exm
# => NoMethodError (undefined method `hello_exm' for KLASS_EX:Class)还有一点值得注意的是,Module类中提供了一个名为module_function(*args)的方法。其可以将指定的方法添加到当前的module中,并且同时在该module被其他类引入时,将方法添加到类的实例中。
module_function :mehtod_name在缺省情况下,module_function调用之后会作用于其后添加的所有方法,类似private。
Rails引入
concern
Rails中的ActiveSupport::Concern通过其中的append_features(base)方法:
def append_features(base)
if base.instance_variable_defined?(:@_dependencies)
base.instance_variable_get(:@_dependencies) << self
return false
else
return false if base < self
@_dependencies.each { |dep| base.include(dep) }
super
# look here!
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
end
end使得引入了ActiveSupport::Concern的module可以在其被其他类include引入时,将其中名为ClassMethods的module中的方法添加到对应类的metaclass中。
module InM
def hello_inm
puts 'hello inm'
end
end
module BaseModel
extend ActiveSupport::Concern
include InM
def hello_base
puts 'hello base module'
end
module ClassMethods
def hello_record
puts 'hello record'
end
end
end
class Re < ApplicationRecord
include BaseModel
end
Re.hello_record
# => hello record而其中的其他方法,或者在该module中通过include引入的其他module中的方法可以被引入到对应类的实例中。
Re.new.hello_base
# => hello base module
Re.new.hello_inm
# => hello inmautoload_path
通过命令行调用rails runner 'puts ActiveSupport::Dependencies.autoload_paths'可以看到Rails配置的autoload_paths:
$ bin/rails runner 'puts ActiveSupport::Dependencies.autoload_paths'
...
/Users/boohee/works/ruby/polestar/app/controllers
/Users/boohee/works/ruby/polestar/app/controllers/concerns
/Users/boohee/works/ruby/polestar/app/jobs
/Users/boohee/works/ruby/polestar/app/mailers
/Users/boohee/works/ruby/polestar/app/models
/Users/boohee/works/ruby/polestar/app/models/concerns
/Users/boohee/works/ruby/polestar/app/services
...这些路径下的module将会被Rails自动加载,这使得我们在使用对应的module时不用添加表示文件结构的命名空间
例如不使用autoload_path的情况下,在app/modules下添加共用的module:
.
└── commons
└── soft_delete.rbsoft_delete.rb文件中module,必须添加命名空间Commons
module Commons::SoftDelete
extend ActiveSupport::Concern
def delete
update(deleted_at: Time.now)
end
def delete!
update!(deleted_at: Time.now)
end
end在其他Record引入时也需要添加命名空间Commons:
class User < ApplicationRecord
include Common::SoftDelete
end我们可以在项目根目录的application.rb中指定Rails自动将app/modules/commons加载,这使得我们可以省去在引入时使用命名空间:
config.autoload_paths += Dir["#{config.root}/app/models/[a-z]*s/"] +
Dir[Rails.root.join("app/workers")]将app/modules/commons/soft_delete.rb文件的module修改为module SoftDelete,Record的引入则可以修改为:
class User < ApplicationRecord
include SoftDelete
end注:autoload_paths 在初始化过程中计算并缓存。目录结构发生变化时,要重启服务器。spring可能会缓存autoload_paths,即使是重启了服务,修改目录后需要暂时关闭spring
最后更新于
这有帮助吗?