Этот вопрос состоит из двух частей.

В книге Ruby Programming Language есть пример (раздел 8.1.1) расширения строкового объекта и класса с помощью модуля.

Первый вопрос. Почему, если вы расширяете класс с помощью нового метода, а затем создаете объект/экземпляр этого класса, вы не можете получить доступ к этому методу?

irb(main):001:0> module Greeter; def ciao; "Ciao!"; end; end
=> nil
irb(main):002:0> String.extend(Greeter)
=> String
irb(main):003:0> String.ciao
=> "Ciao!"
irb(main):004:0> x = "foo bar"
=> "foo bar"
irb(main):005:0> x.ciao
NoMethodError: undefined method `ciao' for "foo bar":String
        from (irb):5
        from :0
irb(main):006:0>

Вторая часть. Когда я пытаюсь расширить объект Fixnum, я получаю ошибку неопределенного метода. Может кто-нибудь объяснить, почему это работает для строки, но не для fixnum?

irb(main):045:0> module Greeter; def ciao; "Ciao!"; end; end
=> nil
irb(main):006:0> 3.extend(Greeter)
TypeError: can't define singleton
        from (irb):6:in `extend_object'
        from (irb):6:in `extend'
        from (irb):6
5
teleball 8 Ноя 2009 в 20:30

2 ответа

Первый вопрос. Почему, если вы расширяете класс с помощью нового метода, а затем создаете объект/экземпляр этого класса, вы не можете получить доступ к этому методу?

Поскольку вы расширили класс String, #ciao является методом класса, а не методом экземпляра.

String.send(:include, Greeter)
x = "foo bar"
x.ciao
# => "Ciao!"

Вторая часть. Когда я пытаюсь расширить объект Fixnum, я получаю ошибку неопределенного метода. Может кто-нибудь объяснить, почему это работает для строки, но не для fixnum?

Вот краткий ответ.

«Fixnums, Symbols, true, nil и false реализованы как непосредственные значения. С непосредственными значениями переменные содержат сами объекты, а не ссылки на них.

Одноэлементные методы не могут быть определены для таких объектов. Два Fixnum с одним и тем же значением всегда представляют один и тот же экземпляр объекта, поэтому (например) переменные экземпляра для Fixnum со значением «один» совместно используются всеми «единицами» в системе. Это делает невозможным определение одноэлементного метода только для одного из них».

Конечно, вы можете включать/расширять класс Fixnum, и каждый экземпляр Fixnum будет предоставлять методы в Mixin. Это именно то, что делает Rails/ActiveSupport, чтобы позволить вам писать

3.days.ago
1.hour.from_now
10
Simone Carletti 8 Ноя 2009 в 20:42
Ваш ответ на первую часть не объясняет (мне) почему. В других языках, когда вы меняете класс и создаете новый объект этого типа класса, вы получаете новый метод. Я делаю предположение, что существует недопустимая связь класса с объектами. Можете ли вы объяснить, почему эта связь здесь недействительна?
 – 
teleball
8 Ноя 2009 в 21:00
Изучение «включения», которое вы мне дали, помогло. Итак, похоже, что теперь, когда я понимаю, что существует разница между расширением и включением, и теперь знаю, что искать, здесь есть сообщение, объясняющее эти различия: stackoverflow.com/questions/156362/…
 – 
teleball
8 Ноя 2009 в 21:10

obj.extend(module) добавляет все методы из module в obj. При вызове как String.extend(Greeter) вы добавляете методы из Greeter в экземпляр Class, который представляет String.

Самый простой способ добавить дополнительные методы экземпляра в существующий класс — повторно открыть класс. Следующие примеры делают то же самое:

class String
  include Greeter
end

class String
  def ciao
    "Ciao!"
  end
end

Fixnums (а также символы, true, false и nil) обрабатываются иначе, чем обычные экземпляры. Два Fixnum с одним и тем же значением всегда будут представлены одним и тем же экземпляром объекта. В результате Ruby не позволяет вам расширять их.

Конечно, вы можете расширить экземпляр любого другого класса, например:

t = "Test"
t.extend(Greeter)
t.ciao
=> "Ciao!"
2
Phil Ross 8 Ноя 2009 в 20:52