класс Module
Расширяет объект модуля с помощью аксессоров класса/модуля и экземпляра для атрибутов класса/модуля, как и встроенные аксессоры attr* для атрибутов экземпляра.
Расширяет API для констант, чтобы иметь возможность работать с квалифицированными именами. Аргументы предполагаются относительными к получателю.
Атрибуты
Общедоступные методы экземпляров
Позволяет создавать псевдонимы для атрибутов, включая методы получения, установки и запроса.
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/aliasing.rb, line 62
def alias_attribute(new_name, old_name)
module_eval " 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
", __FILE__, __LINE__ + 1
end Оборачивает общую структуру:
alias_method :foo_without_feature, :foo alias_method :foo, :foo_with_feature
С этим вы просто делаете:
alias_method_chain :foo, :feature
И оба псевдонима настраиваются для вас.
Методы запроса и с восклицательным знаком (foo?, foo!) сохраняют те же знаки препинания:
alias_method_chain :foo?, :feature
равнозначно
alias_method :foo_without_feature?, :foo? alias_method :foo?, :foo_with_feature?
поэтому вы можете безопасно использовать цепочки foo, foo?, и foo! с одной и той же функцией.
# File activesupport/lib/active_support/core_ext/module/aliasing.rb, line 23
def alias_method_chain(target, feature)
# Strip out punctuation on predicates or bang methods since
# e.g. target?_without_feature is not a valid method name.
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
yield(aliased_target, punctuation) if block_given?
with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
alias_method without_method, target
alias_method target, with_method
case
when public_method_defined?(without_method)
public target
when protected_method_defined?(without_method)
protected target
when private_method_defined?(without_method)
private target
end
end Модуль может или не может иметь имя.
module M; end M.name # => "M" m = Module.new m.name # => nil
Модуль получает имя, когда он впервые присваивается константе. Либо с помощью module или class ключевого слова, либо путём явного присваивания:
m = Module.new # creates an anonymous module M = m # => m gets a name here as a side-effect m.name # => "M"
# File activesupport/lib/active_support/core_ext/module/anonymous.rb, line 16 def anonymous? name.nil? end
Объявляет чтение и запись атрибута, используя внутреннее имя переменной экземпляра.
# File activesupport/lib/active_support/core_ext/module/attr_internal.rb, line 14 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 3
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 8
def attr_internal_writer(*attrs)
attrs.each {|attr_name| attr_internal_define(attr_name, :writer)}
end Предоставляет delegate метод класса для лёгкого экспорта общедоступных методов содержащихся объектов в качестве ваших собственных.
Параметры
-
:to- Указывает целевой объект -
:prefix- Добавляет префикс к новому методу с именем целевого объекта или настраиваемого префикса -
:allow_nil- если установлено в true, предотвращает поднятиеNoMethodError
Макрос принимает одно или несколько имён методов (указанных как символы или строки) и имя целевого объекта через параметр :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'
Если целевой объект является nil и не отвечает на делегированный метод, поднимается NoMethodError , как и в случае с любым другим значением. Иногда, однако, имеет смысл быть устойчивым к этой ситуации, и это цель параметра :allow_nil : Если целевой объект не является nil, или является и отвечает на метод, всё работает как обычно. Но если он является nil и не отвечает на делегированный метод, возвращается nil.
class User < ActiveRecord::Base has_one :profile delegate :age, to: :profile end User.new.age # raises NoMethodError: undefined method `age'
Но если пока нет профиля, это нормально и не должно являться ошибкой:
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 143
def delegate(*methods)
options = methods.pop
unless options.is_a?(Hash) && to = options[:to]
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
end
prefix, allow_nil = options.values_at(:prefix, :allow_nil)
if prefix == true && to =~ /^[^a-z_]/
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
file, line = caller.first.split(':', 2)
line = line.to_i
to = to.to_s
to = 'self.class' if to == 'class'
methods.each do |method|
# Attribute writer methods only accept one argument. Makes sure []=
# methods still accept two arguments.
definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
# The following generated methods call 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_def = [
"def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block)
"_ = #{to}", # _ = client
"if !_.nil? || nil.respond_to?(:#{method})", # if !_.nil? || nil.respond_to?(:name)
" _.#{method}(#{definition})", # _.name(*args, &block)
"end", # end
"end" # end
].join ';'
else
exception = %Q(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
method_def = [
"def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block)
" _ = #{to}", # _ = client
" _.#{method}(#{definition})", # _.name(*args, &block)
"rescue NoMethodError => e", # rescue NoMethodError => e
" if _.nil? && e.name == :#{method}", # if _.nil? && e.name == :name
" #{exception}", # # add helpful message to the exception
" else", # else
" raise", # raise
" end", # end
"end" # end
].join ';'
end
module_eval(method_def, file, line)
end
end deprecate :foo deprecate bar: 'message' deprecate :foo, :bar, baz: 'warning!', qux: 'gone!'
Также можно использовать экземпляр настраиваемого механизма deprecated:
deprecate :foo, deprecator: MyLib::Deprecator.new deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new
Настраиваемые механизмы deprecated должны отвечать на метод 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/deprecation.rb, line 20 def deprecate(*method_names) ActiveSupport::Deprecation.deprecate_methods(self, *method_names) end
# File activesupport/lib/active_support/core_ext/module/method_transplanting.rb, line 5 def foo; end
Определяет методы доступа к атрибутам класса и экземпляра.
module HairColors mattr_accessor :hair_colors end class Person include HairColors end Person.hair_colors = [:brown, :black, :blonde, :red] Person.hair_colors # => [:brown, :black, :blonde, :red] Person.new.hair_colors # => [:brown, :black, :blonde, :red]
Если подкласс изменяет значение, оно также изменится для родительского класса. Аналогично, если родительский класс изменяет значение, это также изменит значение для подклассов.
class Male < Person end Male.hair_colors << :blue Person.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 do
[:brown, :black, :blonde, :red]
end
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 207 def mattr_accessor(*syms, &blk) mattr_reader(*syms, &blk) mattr_writer(*syms, &blk) end
Определяет атрибут класса и создаёт методы чтения класса и экземпляра. Базовое значение переменной класса устанавливается в nil, если оно ещё не определено.
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
Если вы хотите отказаться от создания метода чтения экземпляра, передайте instance_reader: false или instance_accessor:
false.
module HairColors mattr_writer :hair_colors, instance_reader: false end class Person include HairColors end Person.new.hair_colors # => NoMethodError
Также можно передать блок для настройки атрибута с помощью значения по умолчанию.
module HairColors
cattr_reader :hair_colors do
[:brown, :black, :blonde, :red]
end
end
class Person
include HairColors
end
Person.hair_colors # => [:brown, :black, :blonde, :red]
# File activesupport/lib/active_support/core_ext/module/attribute_accessors.rb, line 53
def mattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(" @@#{sym} = nil unless defined? @@#{sym}
def self.#{sym}
@@#{sym}
end
", __FILE__, __LINE__ + 1)
unless options[:instance_reader] == false || options[:instance_accessor] == false
class_eval(" def #{sym}
@@#{sym}
end
", __FILE__, __LINE__ + 1)
end
class_variable_set("@@#{sym}", yield) if block_given?
end
end Определяет атрибут класса и создаёт методы записи класса и экземпляра, чтобы разрешить присваивание атрибуту.
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
Также можно передать блок для настройки атрибута с помощью значения по умолчанию.
class HairColors
mattr_writer :hair_colors do
[:brown, :black, :blonde, :red]
end
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 119
def mattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(" @@#{sym} = nil unless defined? @@#{sym}
def self.#{sym}=(obj)
@@#{sym} = obj
end
", __FILE__, __LINE__ + 1)
unless options[:instance_writer] == false || options[:instance_accessor] == false
class_eval(" def #{sym}=(obj)
@@#{sym} = obj
end
", __FILE__, __LINE__ + 1)
end
send("#{sym}=", yield) if block_given?
end
end Возвращает модуль, который содержит этот модуль в соответствии с его именем.
module M module N end end X = M::N M::N.parent # => M X.parent # => M
Родительским элементом для модулей верхнего уровня и анонимных модулей является Объект.
M.parent # => Object Module.new.parent # => Object
# File activesupport/lib/active_support/core_ext/module/introspection.rb, line 30 def parent parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object end
Возвращает имя модуля, содержащего данный модуль.
M::N.parent_name # => "M"
# File activesupport/lib/active_support/core_ext/module/introspection.rb, line 7
def parent_name
if defined? @parent_name
@parent_name
else
@parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
end
end Возвращает всех родителей данного модуля в соответствии с его именем, упорядоченных от вложенных к внешним. Сам объект не включается в результат.
module M module N end end X = M::N M.parents # => [Object] M::N.parents # => [M, Object] X.parents # => [M, Object]
# File activesupport/lib/active_support/core_ext/module/introspection.rb, line 46
def parents
parents = []
if parent_name
parts = parent_name.split('::')
until parts.empty?
parents << ActiveSupport::Inflector.constantize(parts * '::')
parts.pop
end
end
parents << Object unless parents.include? Object
parents
end # File activesupport/lib/active_support/core_ext/module/qualified_const.rb, line 26
def qualified_const_defined?(path, search_parents=true)
QualifiedConstUtils.raise_if_absolute(path)
QualifiedConstUtils.names(path).inject(self) do |mod, name|
return unless mod.const_defined?(name, search_parents)
mod.const_get(name)
end
return true
end # File activesupport/lib/active_support/core_ext/module/qualified_const.rb, line 36
def qualified_const_get(path)
QualifiedConstUtils.raise_if_absolute(path)
QualifiedConstUtils.names(path).inject(self) do |mod, name|
mod.const_get(name)
end
end # File activesupport/lib/active_support/core_ext/module/qualified_const.rb, line 44 def qualified_const_set(path, value) QualifiedConstUtils.raise_if_absolute(path) const_name = path.demodulize mod_name = path.deconstantize mod = mod_name.empty? ? self : qualified_const_get(mod_name) mod.const_set(const_name, value) end
# File activesupport/lib/active_support/core_ext/module/remove_method.rb, line 8 def redefine_method(method, &block) remove_possible_method(method) define_method(method, &block) end
# File activesupport/lib/active_support/core_ext/module/remove_method.rb, line 2
def remove_possible_method(method)
if method_defined?(method) || private_method_defined?(method)
undef_method(method)
end
end
© 2004–2016 David Heinemeier Hansson
Licensed under the MIT License.