класс Module
Расширяет объект модуля с помощью аксессоров для класса/модуля и экземпляра для атрибутов класса/модуля, точно так же, как и родные аксессоры attr* для атрибутов экземпляра.
Расширяет объект модуля с помощью аксессоров для класса/модуля и экземпляра для атрибутов класса/модуля, точно так же, как и родные аксессоры attr* для атрибутов экземпляра, но делает это на основе потока.
Таким образом, значения ограничены пространством Thread.current под именем класса модуля.
Константы
- DELEGATION_RESERVED_KEYWORDS
- DELEGATION_RESERVED_METHOD_NAMES
- RUBY_RESERVED_KEYWORDS
Атрибуты
Публичные методы экземпляров
# File activesupport/lib/active_support/core_ext/module/aliasing.rb, line 21
def alias_attribute(new_name, old_name)
# The following reader methods use an explicit `self` receiver in order to
# support aliases that start with an uppercase letter. Otherwise, they would
# be resolved as constants instead.
module_eval <<-STR, __FILE__, __LINE__ + 1
def #{new_name}; self.#{old_name}; end # def subject; self.title; end
def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end
def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end
STR
end Позволяет создавать псевдонимы для атрибутов, что включает геттер, сеттер и предикат.
class Content < ActiveRecord::Base # has a title attribute end class Email < Content alias_attribute :subject, :title end e = Email.find(1) e.title # => "Superstars" e.subject # => "Superstars" e.subject? # => true e.subject = "Megastars" e.title # => "Megastars"
# File activesupport/lib/active_support/core_ext/module/anonymous.rb, line 27 def anonymous? name.nil? end
Модуль может или не может иметь имя.
module M; end M.name # => "M" m = Module.new m.name # => nil
anonymous? метод возвращает true, если модуль не имеет имени, и false в противном случае:
Module.new.anonymous? # => true module M; end M.anonymous? # => false
Модуль получает имя, когда он впервые присваивается константе. Либо с помощью ключевого слова module или class, либо путём явного присваивания:
m = Module.new # creates an anonymous module m.anonymous? # => true M = m # m gets a name here as a side-effect m.name # => "M" m.anonymous? # => false
# File activesupport/lib/active_support/core_ext/module/attr_internal.rb, line 16 def attr_internal_accessor(*attrs) attr_internal_reader(*attrs) attr_internal_writer(*attrs) end
Объявляет читатель и записыватель атрибута, поддерживаемый внутренним именем переменной экземпляра.
# File activesupport/lib/active_support/core_ext/module/attr_internal.rb, line 5
def attr_internal_reader(*attrs)
attrs.each { |attr_name| attr_internal_define(attr_name, :reader) }
end Объявляет читатель атрибута, поддерживаемый внутренним именем переменной экземпляра.
# File activesupport/lib/active_support/core_ext/module/attr_internal.rb, line 10
def attr_internal_writer(*attrs)
attrs.each { |attr_name| attr_internal_define(attr_name, :writer) }
end Объявляет записыватель атрибута, поддерживаемый внутренним именем переменной экземпляра.
# File activesupport/lib/active_support/core_ext/module/delegation.rb, line 171
def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil)
unless to
raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)."
end
if prefix == true && /^[^a-z_]/.match?(to)
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
end
method_prefix = \
if prefix
"#{prefix == true ? to : prefix}_"
else
""
end
location = caller_locations(1, 1).first
file, line = location.path, location.lineno
to = to.to_s
to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
method_def = []
method_names = []
methods.map do |method|
method_name = prefix ? "#{method_prefix}#{method}" : method
method_names << method_name.to_sym
# Attribute writer methods only accept one argument. Makes sure []=
# methods still accept two arguments.
definition = if /[^\]]=$/.match?(method)
"arg"
elsif RUBY_VERSION >= "2.7"
"..."
else
"*args, &block"
end
# The following generated method calls the target exactly once, storing
# the returned value in a dummy variable.
#
# Reason is twofold: On one hand doing less calls is in general better.
# On the other hand it could be that the target has side-effects,
# whereas conceptually, from the user point of view, the delegator should
# be doing one call.
if allow_nil
method = method.to_s
method_def <<
"def #{method_name}(#{definition})" <<
" _ = #{to}" <<
" if !_.nil? || nil.respond_to?(:#{method})" <<
" _.#{method}(#{definition})" <<
" end" <<
"end"
else
method = method.to_s
method_name = method_name.to_s
method_def <<
"def #{method_name}(#{definition})" <<
" _ = #{to}" <<
" _.#{method}(#{definition})" <<
"rescue NoMethodError => e" <<
" if _.nil? && e.name == :#{method}" <<
%( raise DelegationError, "#{self}##{method_name} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") <<
" else" <<
" raise" <<
" end" <<
"end"
end
end
module_eval(method_def.join(";"), file, line)
private(*method_names) if private
method_names
end Предоставляет delegate метод класса для лёгкого экспонирования публичных методов содержащихся объектов как своих собственных.
Параметры
-
:to- Указывает имя целевого объекта в виде символа или строки -
:prefix- Добавляет префикс к новому методу с именем целевого объекта или настраиваемым префиксом -
:allow_nil- Если установлено в true, предотвращает поднятиеModule::DelegationError -
:private- Если установлено в true, изменяет видимость метода на private
Макрокоманда получает одно или несколько имён методов (указанных в виде символов или строк) и имя целевого объекта через параметр :to (также символ или строка).
Делегирование особенно полезно с ассоциациями Active Record:
class Greeter < ActiveRecord::Base
def hello
'hello'
end
def goodbye
'goodbye'
end
end
class Foo < ActiveRecord::Base
belongs_to :greeter
delegate :hello, to: :greeter
end
Foo.new.hello # => "hello"
Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
Разрешено несколько делегатов одному целевому объекту:
class Foo < ActiveRecord::Base belongs_to :greeter delegate :hello, :goodbye, to: :greeter end Foo.new.goodbye # => "goodbye"
Методы могут быть делегированы переменным экземпляра, класса или константам, указав их в виде символов:
class Foo
CONSTANT_ARRAY = [0,1,2,3]
@@class_array = [4,5,6,7]
def initialize
@instance_array = [8,9,10,11]
end
delegate :sum, to: :CONSTANT_ARRAY
delegate :min, to: :@@class_array
delegate :max, to: :@instance_array
end
Foo.new.sum # => 6
Foo.new.min # => 4
Foo.new.max # => 11
Также возможно делегировать метод классу, используя :class:
class Foo
def self.hello
"world"
end
delegate :hello, to: :class
end
Foo.new.hello # => "world"
Делегаты могут иметь опциональный префикс с помощью параметра :prefix. Если значение равно true, методы делегата имеют префикс, соответствующий имени делегируемого объекта.
Person = Struct.new(:name, :address)
class Invoice < Struct.new(:client)
delegate :name, :address, to: :client, prefix: true
end
john_doe = Person.new('John Doe', 'Vimmersvej 13')
invoice = Invoice.new(john_doe)
invoice.client_name # => "John Doe"
invoice.client_address # => "Vimmersvej 13"
Также возможно задать настраиваемый префикс.
class Invoice < Struct.new(:client) delegate :name, :address, to: :client, prefix: :customer end invoice = Invoice.new(john_doe) invoice.customer_name # => 'John Doe' invoice.customer_address # => 'Vimmersvej 13'
Делегированные методы по умолчанию являются публичными. Используйте private: true для изменения этого.
class User < ActiveRecord::Base
has_one :profile
delegate :first_name, to: :profile
delegate :date_of_birth, to: :profile, private: true
def age
Date.today.year - date_of_birth.year
end
end
User.new.first_name # => "Tomas"
User.new.date_of_birth # => NoMethodError: private method `date_of_birth' called for #<User:0x00000008221340>
User.new.age # => 2
Если целевой объект nil и не отвечает на делегированный метод, будет поднято исключение Module::DelegationError. Если же нужно вернуть nil, используйте параметр :allow_nil
class User < ActiveRecord::Base has_one :profile delegate :age, to: :profile end User.new.age # => Module::DelegationError: User#age delegated to profile.age, but profile is nil
Но если отсутствие профиля не является проблемой и не должно быть ошибкой:
class User < ActiveRecord::Base has_one :profile delegate :age, to: :profile, allow_nil: true end User.new.age # nil
Обратите внимание, что если целевой объект не nil, вызов делается независимо от параметра :allow_nil, и поэтому исключение всё равно будет поднято, если объект не отвечает на метод:
class Foo
def initialize(bar)
@bar = bar
end
delegate :name, to: :@bar, allow_nil: true
end
Foo.new("Bar").name # raises NoMethodError: undefined method `name'
Метод целевого объекта должен быть публичным, иначе будет поднято исключение NoMethodError.
# File activesupport/lib/active_support/core_ext/module/delegation.rb, line 295
def delegate_missing_to(target, allow_nil: nil)
target = target.to_s
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def respond_to_missing?(name, include_private = false)
# It may look like an oversight, but we deliberately do not pass
# +include_private+, because they do not get delegated.
return false if name == :marshal_dump || name == :_dump
#{target}.respond_to?(name) || super
end
def method_missing(method, *args, &block)
if #{target}.respond_to?(method)
#{target}.public_send(method, *args, &block)
else
begin
super
rescue NoMethodError
if #{target}.nil?
if #{allow_nil == true}
nil
else
raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
end
else
raise
end
end
end
end
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
RUBY
end При создании декораторов часто возникает следующая модель:
class Partition
def initialize(event)
@event = event
end
def person
detail.person || creator
end
private
def respond_to_missing?(name, include_private = false)
@event.respond_to?(name, include_private)
end
def method_missing(method, *args, &block)
@event.send(method, *args, &block)
end
end
С Module#delegate_missing_to, вышеперечисленное сокращается до:
class Partition
delegate_missing_to :@event
def initialize(event)
@event = event
end
def person
detail.person || creator
end
end
Целевым объектом может быть что угодно, вызываемое в объекте, например, переменные экземпляра, методы, константы и т. д.
Делегированный метод должен быть публичным в целевом объекте, иначе будет поднято исключение DelegationError. Если нужно вернуть nil, используйте параметр :allow_nil.
Методы marshal_dump и _dump исключены из делегирования из-за возможного вмешательства при вызове Marshal.dump(object), если метод делегации целевого объекта object добавляет или удаляет переменные экземпляра.
# File activesupport/lib/active_support/core_ext/module/deprecation.rb, line 22 def deprecate(*method_names) ActiveSupport::Deprecation.deprecate_methods(self, *method_names) end
deprecate :foo deprecate bar: 'message' deprecate :foo, :bar, baz: 'warning!', qux: 'gone!'
Также можно использовать экземпляр настраиваемого обработчика устаревших методов:
deprecate :foo, deprecator: MyLib::Deprecator.new deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new
Настраиваемые обработчики устаревших методов должны отвечать на метод deprecation_warning(deprecated_method_name, message, caller_backtrace), где можно реализовать поведение пользовательского предупреждения.
class MyLib::Deprecator
def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
Kernel.warn message
end
end
# File activesupport/lib/active_support/core_ext/module/attribute_accessors.rb, line 200 def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk) location = caller_locations(1, 1).first mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, location: location, &blk) mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default, location: location) end
Определяет как методы доступа к атрибутам класса, так и методы доступа к атрибутам экземпляра. Все созданные методы класса и экземпляра будут публичными, даже если этот метод вызывается с модификаторами доступа private или protected.
module HairColors mattr_accessor :hair_colors end class Person include HairColors end HairColors.hair_colors = [:brown, :black, :blonde, :red] HairColors.hair_colors # => [:brown, :black, :blonde, :red] Person.new.hair_colors # => [:brown, :black, :blonde, :red]
Если подкласс изменяет значение, это также изменяет значение для родительского класса. Аналогично, если родительский класс изменяет значение, это также изменяет значение для подклассов.
class Citizen < Person end Citizen.new.hair_colors << :blue Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue]
Чтобы опустить метод записи экземпляра, передайте instance_writer: false. Чтобы опустить метод чтения экземпляра, передайте instance_reader: false.
module HairColors mattr_accessor :hair_colors, instance_writer: false, instance_reader: false end class Person include HairColors end Person.new.hair_colors = [:brown] # => NoMethodError Person.new.hair_colors # => NoMethodError
Или передайте instance_accessor: false, чтобы опустить оба метода экземпляра.
module HairColors mattr_accessor :hair_colors, instance_accessor: false end class Person include HairColors end Person.new.hair_colors = [:brown] # => NoMethodError Person.new.hair_colors # => NoMethodError
Можно задать значение по умолчанию для атрибута.
module HairColors
mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red]
end
class Person
include HairColors
end
Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
# File activesupport/lib/active_support/core_ext/module/attribute_accessors.rb, line 51
def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil, location: nil)
raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
location ||= caller_locations(1, 1).first
definition = []
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
definition << "def self.#{sym}; @@#{sym}; end"
if instance_reader && instance_accessor
definition << "def #{sym}; @@#{sym}; end"
end
sym_default_value = (block_given? && default.nil?) ? yield : default
class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}")
end
module_eval(definition.join(";"), location.path, location.lineno)
end Определяет атрибут класса и создаёт методы чтения для класса и экземпляра. Базовая переменная класса устанавливается в nil, если она не определена ранее. Все созданные методы класса и экземпляра будут публичными, даже если этот метод вызывается с модификаторами доступа private или protected.
module HairColors
mattr_reader :hair_colors
end
HairColors.hair_colors # => nil
HairColors.class_variable_set("@@hair_colors", [:brown, :black])
HairColors.hair_colors # => [:brown, :black]
Имя атрибута должно быть допустимым именем метода в Ruby.
module Foo mattr_reader :"1_Badname" end # => NameError: invalid attribute name: 1_Badname
Чтобы опустить метод чтения экземпляра, передайте instance_reader: false или instance_accessor: false.
module HairColors mattr_reader :hair_colors, instance_reader: false end class Person include HairColors end Person.new.hair_colors # => NoMethodError
Можно задать значение по умолчанию для атрибута.
module HairColors mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red] end class Person include HairColors end Person.new.hair_colors # => [:brown, :black, :blonde, :red]
# File activesupport/lib/active_support/core_ext/module/attribute_accessors.rb, line 115
def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil, location: nil)
raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
location ||= caller_locations(1, 1).first
definition = []
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
definition << "def self.#{sym}=(val); @@#{sym} = val; end"
if instance_writer && instance_accessor
definition << "def #{sym}=(val); @@#{sym} = val; end"
end
sym_default_value = (block_given? && default.nil?) ? yield : default
class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}")
end
module_eval(definition.join(";"), location.path, location.lineno)
end Определяет атрибут класса и создаёт методы записи для класса и экземпляра, позволяющие назначать значение атрибуту. Все созданные методы класса и экземпляра будут публичными, даже если этот метод вызван с модификатором доступа private или protected.
module HairColors
mattr_writer :hair_colors
end
class Person
include HairColors
end
HairColors.hair_colors = [:brown, :black]
Person.class_variable_get("@@hair_colors") # => [:brown, :black]
Person.new.hair_colors = [:blonde, :red]
HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red]
Чтобы опустить метод записи экземпляра, передайте instance_writer: false или instance_accessor: false.
module HairColors mattr_writer :hair_colors, instance_writer: false end class Person include HairColors end Person.new.hair_colors = [:blonde, :red] # => NoMethodError
Можно задать значение по умолчанию для атрибута.
module HairColors
mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red]
end
class Person
include HairColors
end
Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
# File activesupport/lib/active_support/core_ext/module/introspection.rb, line 35 def module_parent module_parent_name ? ActiveSupport::Inflector.constantize(module_parent_name) : Object end
Возвращает модуль, содержащий этот модуль в соответствии с его именем.
module M module N end end X = M::N M::N.module_parent # => M X.module_parent # => M
Родителем модулей верхнего уровня и анонимных модулей является Object.
M.module_parent # => Object Module.new.module_parent # => Object
# File activesupport/lib/active_support/core_ext/module/introspection.rb, line 10
def module_parent_name
if defined?(@parent_name)
@parent_name
else
parent_name = name =~ /::[^:]+\z/ ? -$` : nil
@parent_name = parent_name unless frozen?
parent_name
end
end Возвращает имя модуля, содержащего этот модуль.
M::N.module_parent_name # => "M"
# File activesupport/lib/active_support/core_ext/module/introspection.rb, line 51
def module_parents
parents = []
if module_parent_name
parts = module_parent_name.split("::")
until parts.empty?
parents << ActiveSupport::Inflector.constantize(parts * "::")
parts.pop
end
end
parents << Object unless parents.include? Object
parents
end Возвращает всех родителей этого модуля в соответствии с его именем, упорядоченных от вложенных к внешним. Получатель не включён в результат.
module M module N end end X = M::N M.module_parents # => [Object] M::N.module_parents # => [M, Object] X.module_parents # => [M, Object]
# File activesupport/lib/active_support/core_ext/module/redefine_method.rb, line 17 def redefine_method(method, &block) visibility = method_visibility(method) silence_redefinition_of_method(method) define_method(method, &block) send(visibility, method) end
Заменяет существующее определение метода, если оно есть, на переданный блок в качестве его тела.
# File activesupport/lib/active_support/core_ext/module/redefine_method.rb, line 26 def redefine_singleton_method(method, &block) singleton_class.redefine_method(method, &block) end
Заменяет существующее определение одиночного метода, если оно есть, на переданный блок в качестве его тела.
# File activesupport/lib/active_support/core_ext/module/remove_method.rb, line 7
def remove_possible_method(method)
if method_defined?(method) || private_method_defined?(method)
undef_method(method)
end
end Удаляет метод с указанным именем, если он существует.
# File activesupport/lib/active_support/core_ext/module/remove_method.rb, line 14 def remove_possible_singleton_method(method) singleton_class.remove_possible_method(method) end
Удаляет одиночный метод с указанным именем, если он существует.
# File activesupport/lib/active_support/core_ext/module/redefine_method.rb, line 7
def silence_redefinition_of_method(method)
if method_defined?(method) || private_method_defined?(method)
# This suppresses the "method redefined" warning; the self-alias
# looks odd, but means we don't need to generate a unique name
alias_method method, method
end
end Помечает метод с указанным именем как предназначенный для повторного определения, если он существует. Подавляет предупреждение Ruby о повторном определении метода. Предпочтительнее использовать redefine_method, когда это возможно.
# File activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb, line 143 def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil) thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default) thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor) end
Определяет методы доступа как для класса, так и для экземпляров атрибутов класса.
class Account thread_mattr_accessor :user end Account.user = "DHH" Account.user # => "DHH" Account.new.user # => "DHH"
Если подкласс изменяет значение, значение родительского класса не изменяется. Аналогично, если родительский класс изменяет значение, значение подклассов не изменяется.
class Customer < Account end Customer.user = "Rafael" Customer.user # => "Rafael" Account.user # => "DHH"
Чтобы опустить метод записи экземпляра, передайте instance_writer: false. Чтобы опустить метод чтения экземпляра, передайте instance_reader: false.
class Current thread_mattr_accessor :user, instance_writer: false, instance_reader: false end Current.new.user = "DHH" # => NoMethodError Current.new.user # => NoMethodError
Или передайте instance_accessor: false, чтобы опустить оба метода экземпляра.
class Current thread_mattr_accessor :user, instance_accessor: false end Current.new.user = "DHH" # => NoMethodError Current.new.user # => NoMethodError
© 2004–2020 David Heinemeier Hansson
Licensed under the MIT License.