class Module
Доступ к атрибутам
Расширяет объект модуля классами/модулями и доступом к экземплярам атрибутов класса/модуля, так же, как и родные атрибуты для атрибутов экземпляров.
Доступ к атрибутам на поток
Расширяет объект модуля классами/модулями и доступом к экземплярам атрибутов класса/модуля, так же, как и родные атрибуты для атрибутов экземпляров, но делает это на основе потока.
Таким образом, значения ограничены пространством Thread.current под именем класса модуля.
Обратите внимание, что это также может быть ограничено каждым волокном, если Rails.application.config.active_support.isolation_level установлено в :fiber.
Константы
- DELEGATION_RESERVED_KEYWORDS
- DELEGATION_RESERVED_METHOD_NAMES
- RUBY_ЗАРЕЗЕРВИРОВАННЫЕ_КЛЮЧЕВЫЕ_СЛОВА
Атрибуты
Публичные методы экземпляров
# 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/object/deep_dup.rb, line 64
def deep_dup
if name.nil?
super
else
self
end
end Возвращает копию модуля или класса, если он анонимный. Если он именован, возвращает self.
Object.deep_dup == Object # => true klass = Class.new klass.deep_dup == klass # => false
Object#deep_dup # 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
receiver = to.to_s
receiver = "self.#{receiver}" if DELEGATION_RESERVED_METHOD_NAMES.include?(receiver)
method_def = []
method_names = []
method_def << "self.private" if private
methods.each 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 /[^\]]=\z/.match?(method)
"arg"
else
method_object =
begin
if to.is_a?(Module)
to.method(method)
elsif receiver == "self.class"
method(method)
end
rescue NameError
# Do nothing. Fall back to `"..."`
end
if method_object
parameters = method_object.parameters
if (parameters.map(&:first) & [:opt, :rest, :keyreq, :key, :keyrest]).any?
"..."
else
defn = parameters.filter_map { |type, arg| arg if type == :req }
defn << "&block"
defn.join(", ")
end
else
"..."
end
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})" <<
" _ = #{receiver}" <<
" 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})" <<
" _ = #{receiver}" <<
" _.#{method}(#{definition})" <<
"rescue NoMethodError => e" <<
" if _.nil? && e.name == :#{method}" <<
%( raise DelegationError, "#{self}##{method_name} delegated to #{receiver}.#{method}, but #{receiver} is nil: \#{self.inspect}") <<
" else" <<
" raise" <<
" end" <<
"end"
end
end
module_eval(method_def.join(";"), file, line)
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'
Делегированные методы по умолчанию являются public. Передайте 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 318
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)
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 17
def deprecate(*method_names, deprecator: nil, **options)
if deprecator.is_a?(ActiveSupport::Deprecation)
deprecator.deprecate_methods(self, *method_names, **options)
elsif deprecator
# we just need any instance to call deprecate_methods, but the deprecation will be emitted by deprecator
ActiveSupport.deprecator.deprecate_methods(self, *method_names, **options, deprecator: deprecator)
else
ActiveSupport.deprecator.warn("Module.deprecate without a deprecator is deprecated")
ActiveSupport::Deprecation._instance.deprecate_methods(self, *method_names, **options)
end
end deprecate :foo, deprecator: MyLib.deprecator deprecate :foo, bar: "warning!", deprecator: MyLib.deprecator
Депрекатор, как правило, является экземпляром ActiveSupport::Deprecation, но вы также можете передать любой объект, который отвечает на 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 208 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
Определяет как классовые, так и экземплярные методы доступа для атрибутов класса. Все созданные методы класса и экземпляра будут public, даже если этот метод был вызван с модификатором доступа 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]
mattr_accessor(:hair_styles) { [:long, :short] }
end
class Person
include HairColors
end
Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
Person.class_variable_get("@@hair_styles") # => [:long, :short]
# File activesupport/lib/active_support/core_ext/module/attribute_accessors.rb, line 55
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]
mattr_reader(:hair_styles) { [:long, :short] }
end
class Person
include HairColors
end
Person.new.hair_colors # => [:brown, :black, :blonde, :red]
Person.new.hair_styles # => [:long, :short]
# File activesupport/lib/active_support/core_ext/module/attribute_accessors.rb, line 121
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]
mattr_writer(:hair_styles) { [:long, :short] }
end
class Person
include HairColors
end
Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
Person.class_variable_get("@@hair_styles") # => [:long, :short]
# File activesupport/lib/active_support/core_ext/module/introspection.rb, line 34 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 9
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 50
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 170 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"
В отличие от mattr_accessor, значения не разделяются с подклассами или родительскими классами. Если подкласс изменяет значение, значение родительского класса не изменяется. Если родительский класс изменяет значение, значение подклассов не изменяется.
class Customer < Account end Account.user # => "DHH" Customer.user # => nil 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
Значение по умолчанию может быть указано с помощью опции :default. Поскольку к значению по умолчанию могут иметь доступ несколько потоков, значения по умолчанию, не являющиеся замороженными, будут dup и заморожены.
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.