класс Module
Доступ к атрибутам
Расширяет объект модуля с доступом к атрибутам класса/модуля и экземпляра, подобно встроенным аксессорам attr* для атрибутов экземпляра.
Доступ к атрибутам на нить
Расширяет объект модуля с доступом к атрибутам класса/модуля и экземпляра, подобно встроенным аксессорам attr* для атрибутов экземпляра, но делает это на основе отдельной нити.
Значения ограничены областью действия Thread.current под именем класса модуля.
Обратите внимание, что область действия также может быть ограничена на основе волокна, если Rails.application.config.active_support.isolation_level установлено в :fiber.
Атрибуты
Публичные методы класса
# File activesupport/lib/active_support/core_ext/module/attr_internal.rb, line 25
def attr_internal_naming_format=(format)
if format.start_with?("@")
ActiveSupport.deprecator.warn <<~MESSAGE
Setting `attr_internal_naming_format` with a `@` prefix is deprecated and will be removed in Rails 8.0.
You can simply replace #{format.inspect} by #{format.delete_prefix("@").inspect}.
MESSAGE
format = format.delete_prefix("@")
end
@attr_internal_naming_format = format
end Общедоступные методы экземпляров
# 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 160
def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil)
::ActiveSupport::Delegation.generate(
self,
methods,
location: caller_locations(1, 1).first,
to: to,
prefix: prefix,
allow_nil: allow_nil,
private: private,
)
end Предоставляет метод класса delegate, чтобы легко экспонировать публичные методы содержащихся объектов как свои собственные.
Параметры
-
:to- Указывает имя целевого объекта как символ или строку -
:prefix- Добавляет к новому методу имя целевого или настраиваемый префикс -
:allow_nil- Если установлено в true, предотвращает поднятиеActiveSupport::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 и не отвечает делегированному методу, поднимается ActiveSupport::DelegationError. Если вы хотите вместо этого вернуть nil, используйте параметр :allow_nil
class User < ActiveRecord::Base has_one :profile delegate :age, to: :profile end User.new.age # => ActiveSupport::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 218
def delegate_missing_to(target, allow_nil: nil)
::ActiveSupport::Delegation.generate_method_missing(
self,
target,
allow_nil: allow_nil,
)
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
Целевой объект может быть любым вызываемым объектом внутри объекта, например переменными экземпляра, методами, константами и т. д.
Делегированный метод должен быть общедоступным в целевом объекте, иначе он поднимет ActiveSupport::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:, **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)
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
Определяет чтение и запись атрибутов класса и экземпляра. Все созданные методы класса и экземпляра будут общедоступными, даже если этот метод был вызван с модификатором доступа 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.