class Module
Доступ к атрибутам
Расширяет объект модуля с помощью доступа к атрибутам класса/модуля и экземпляра, как и встроенные accessors attr* для атрибутов экземпляра.
Доступ к атрибутам на основе потока
Расширяет объект модуля с помощью доступа к атрибутам класса/модуля и экземпляра, как и встроенные accessors attr* для атрибутов экземпляра, но делает это на основе каждого потока.
Таким образом, значения ограничены пространством Thread.current под именем класса модуля.
Обратите внимание, что они также могут быть ограничены по волокну, если Rails.application.config.active_support.isolation_level задано как `:fiber`
Константы
- 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 = /[^\]]=\z/.match?(method) ? "arg" : "..."
# 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'
Методы по умолчанию являются 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'
Целевой метод должен быть public, в противном случае будет вызвано NoMethodError.
# File activesupport/lib/active_support/core_ext/module/delegation.rb, line 289
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 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 202 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]
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 53
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, если она не определена ранее. Все созданные классовые и экземплярные методы будут public, даже если этот метод вызван с модификатором доступа 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 117
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 152 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
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.