Is it possible to mixin before the constructor is called? - ruby

I thought I'd make a mixin for logging to keep my code DRY. This is how it looks:
# Mixin that provides shortcuts for logging methods.
module Logging
def self.included(base)
base.class_exec {
#logger_name = base.to_s
#subloggers = []
#logger = Logger.new(STDOUT)
#logger.level = Logger::FATAL
}
end
def logger=(logger)
#logger = logger
#subloggers.each { |obj| obj.logger = logger }
end
def debug(&block)
#logger.debug(#logger_name, &block)
end
def info(&block)
#logger.info(#logger_name, &block)
end
def warn(&block)
#logger.warn(#logger_name, &block)
end
def error(&block)
#logger.error(#logger_name, &block)
end
def fatal(&block)
#logger.fatal(#logger_name, &block)
end
end
In theory, I should now be able to do this:
class SomeClass
include Logging
def foo_bar
debug { "foo_bar is being executed" }
fatal { "IT'S A TRAP" }
end
end
The problem is that apparently initialize is called before the mixins are included, causing this to fail:
class SomeClass
include Logging
def initialize
#cache = CacheClass.new
#subloggers << #cache # #subloggers is nil
end
end
And I can't picture any way around it. I always create my dependencies in the constructor, and I need the Logging mixin to be available at that point. Any ideas?

This is wrong:
The problem is that apparently initialize is called before the mixins are included, causing this to fail:
Contrary to what you wrote, initialize is not called. #subloggers is not initialized for the SomeClass instance. Are you thinking that #subloggers assigned in Logging.included(base) is the same variable as the #subloggers you are accessing from the SomeClass constructor? That should not be the case. An instance variable cannot be created before creation of the instance itself.

I think when you mix in that class, those variables become instance variables on the class. Not to be confused with class variables, or regular instance variables. Check out this little irb conversation:
1.9.3p194 :012 > class Foo
1.9.3p194 :013?> def self.bar
1.9.3p194 :014?> #bar
1.9.3p194 :015?> end
1.9.3p194 :016?> def self.bar=(x)
1.9.3p194 :017?> #bar=x
1.9.3p194 :018?> end
1.9.3p194 :019?> def self.show
1.9.3p194 :020?> puts #bar
1.9.3p194 :021?> end
1.9.3p194 :022?> end
=> nil
1.9.3p194 :023 > Foo.bar=5
=> 5
1.9.3p194 :024 > Foo.bar
=> 5
1.9.3p194 :025 > Foo.show
5
=> nil
1.9.3p194 :026 > a=Foo.new
=> #<Foo:0x007fe9038e5dc0>
1.9.3p194 :029 > a.instance_variables
=> []
1.9.3p194 :030 > a.class.instance_variables
=> [:#bar]
Compare the results of your classes:
SomeClass.instance_variables
SomeClass.new.instance_variables
SomeClass.new.class.instance_variables

Related

Can someone help explain the post_initialize callback for class creation (Sandi Metz)

I am reading Sandi Metz's POODR and have come across a coding principle that I don't quite understand. Here is the code:
class Bicycle
attr_reader :size, :chain, :tire_size
def initialize(args = {})
#size = args[:size] || 1
#chain = args[:chain] || 2
#tire_size = args[:tire_size] || 3
post_initialize(args)
end
end
class MountainBike < Bicycle
attr_reader :front_shock, :rear_shock
def post_initialize(args)
#front_shock = args[:front_shock]
#rear_shock = args[:rear_shock]
end
end
mb = MountainBike.new(front_shock: 4, rear_shock: 5)
puts mb.size
puts mb.chain
puts mb.tire_size
puts mb.front_shock
puts mb.rear_shock
This code will output 1,2,3,4,5 for its respective attributes. What I don't understand is the method look up.
When a mountain bike is instantiated, because it does not have its own initialize method, it will travel up the method lookup chain onto its super class (Bicycle). But now from there, it seems that Bicycle then moves back down into MountainBike's post_initialize method. Instead of continuing up the method chain, how can it go back down? Is post_initialize a ruby keyword like initialize in that it serves some sort of special function? Is there some other ruby introspection methods I can use to see what is going on?
The important thing to understand here is that in this code:
def initialize(args = {})
# ...
post_initialize(args)
end
...post_initialize has an implicit receiver, self. In other words, post_initialize(args) here is equivalent† to self.post_initialize(args), and self is an instance of MountainBike. Method lookup always starts with the receiver's class,‡ so it has no trouble finding MountainBike#post_initialize.
† That's a lie; it's not equivalent when it comes to privacy; private methods cannot be called with an explicit receiver.
‡ That's also a lie; it actually starts with the receiver's singleton class, but then it tries its class.
There is nothing special about the post_initialize method. It is merely a plain vanilla instance method of the subclass.
In Ruby, a superclass instance method is able to call a subclass instance method, even in its constructor. Check out this irb session:
2.3.0 :003 > class Base
2.3.0 :004?> def initialize
2.3.0 :005?> foo
2.3.0 :006?> end
2.3.0 :007?> end
=> :initialize
2.3.0 :015 > class Derived < Base
2.3.0 :016?> def foo
2.3.0 :017?> puts 'I am foo.'
2.3.0 :018?> end
2.3.0 :019?> end
=> :foo
2.3.0 :020 > Derived.new
I am foo.
The usual way this is done is by having the subclass call super, but I guess Sandi is suggesting the post_initialize approach to require that the subclass provide its own initialization, or formally decline to do so by implementing an empty method. (Plus, writers of subclasses may forget to call super.) Here is how it would be done with super:
2.3.0 :001 > class Base
2.3.0 :002?> def initialize
2.3.0 :003?> puts 'in base'
2.3.0 :004?> end
2.3.0 :005?> end
=> :initialize
=> #<Derived:0x007fda6ba291d8>
2.3.0 :012 > class Derived < Base
2.3.0 :013?> def initialize
2.3.0 :014?> super
2.3.0 :015?> puts 'in derived'
2.3.0 :016?> end
2.3.0 :017?> end
=> :initialize
2.3.0 :018 > Derived.new
in base
in derived
=> #<Derived:0x007fda6b104b98>

Why can an instance method be called as a module method after include?

An instance method defined on a module:
module A
def foo; :bar end
end
seems to be able to be called as a module method of that module when that module is included:
include A
A.foo # => :bar
Why is that?
You're including A into Object.
module A
def self.included(base)
puts base.inspect #Object
end
def foo
:bar
end
end
include A
puts A.foo # :bar
puts 2.foo # :bar
#puts BasicObject.new.foo #this will fail
Also note that the top level object main is special; it's both an instance of Object and a sort of delegator to Object.
See http://banisterfiend.wordpress.com/2010/11/23/what-is-the-ruby-top-level/
Tried this out in irb, it included it in Object. include A also returns Object
irb > module A
irb > def foo; :bar end
irb > end
=> nil
irb > Object.methods.include? :foo
=> false
irb > include A
=> Object
irb > Object.methods.include? :foo
=> true

Aliasing a setter method

class Test
attr_accessor :something
end
class Test
alias :old_something= :something=
def something=(a)
a += 2 # do something with the argument
old_something=(a) # then pass it on
end
end
I would expect that if I said
t = Test.new
t.something = 3
puts t.something
It would print out 5. But it prints nil. Why does this not work?
The form
foo = bar
assigns to a local variable. You need to make it explicit that you want to call a method:
self.foo = bar
Edit: #Jörg's answer is probably the real problem, but my original answer below may be helpful as well. Redacted some misleading details from my old answer.
If you drop the def something= entirely, you'll get a setter for something which has an alias of old_something:
class Test
attr_accessor :something
end
class Test
alias :old_something= :something=
end
1.9.3p327 :001 > require "./test.rb"
=> true
1.9.3p327 :002 > t = Test.new
=> #<Test:0x000000018eb740>
1.9.3p327 :003 > t.something = "blah"
=> "blah"
1.9.3p327 :004 > t.something
=> "blah"
1.9.3p327 :005 > t.old_something = "moo"
=> "moo"
1.9.3p327 :006 > t.something
=> "moo"
The reason is that #something is never assigned to anything.
This code will do what you are trying to do:
class Test
attr_accessor :something
end
class Test
alias :old_something= :something=
def something=(a)
#something = a += 2 # do something with the argument
old_something=(a) # then pass it on
end
end
t = Test.new
t.something=( 3)
puts t.something
The difference was assinging the instance variable #something to a value, and then allowing your code to increment it by the a variable that was passed in.

Extending a ruby eigenclass to load CarrierWave

Update: I've simplified my question; you can see the full history by checking out my editing revisions. Thanks to iain and bernardk for getting me this far.
I want to load carrierwave functionality into an instance of my User < ActiveRecord::Base model.
require 'uploaders/avatar_uploader'
module HasAnAvatar
def self.extended(host)
if host.class == Class
mount_uploader :avatar, AvatarUploader
else
class << host
mount_uploader :avatar, AvatarUploader
end
end
end
end
Executing:
(user = User.first).extend(HasAnAvatar).avatar
Results in:
NoMethodError: undefined method new' for nil:NilClass from
/Users/evan/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/carrierwave-0.6.2/lib/carrierwave/mount.rb:306:in
uploader'
I suspect the problem is that mount_uploader in HasAnAvatar is not being invoked properly on the eigenclass for user, such that the uploaders hash isn't populated.
Any thoughts on how to get this working?
Here is an example Rails app for this issue: https://github.com/neezer/extend_with_avatar_example
Here are two ways you can can include a module into an instance (which is to basically extend the instance's eigenclass). I don't think this is the best answer to your problem though, even if it may answer the question (in part).
class A
end
# => nil
module B
def blah
"Blah!"
end
end
# => nil
a = A.new
=> #<A:0x0000010086cdf0>
a.blah
# NoMethodError: undefined method `blah' for #<A:0x0000010086cdf0>
class << a
include B
end
a.blah
# => "Blah!"
b = A.new
# => #<A:0x0000010083b818>
b.blah
# NoMethodError: undefined method `blah' for #<A:0x0000010083b818>
b.extend B
# => #<A:0x0000010083b818>
b.blah
# => "Blah!"
c.blah
# NoMethodError: undefined method `blah' for #<A:0x0000010085eed0>
From your edit:
module Pacifiable
def pacified_with(mechanism)
class_eval do
define_method(:"pacified_with_#{mechanism}?") { true }
end
end
end
# => nil
class JellyFish
define_method(:is_squishy?) { true }
end
# => #<Proc:0x00000100850448#(irb):10 (lambda)>
class Lobster
extend Pacifiable
pacified_with :polar_bear
define_method(:is_squishy?) { false }
end
# => #<Proc:0x00000100960540#(irb):16 (lambda)>
lobster = Lobster.new
# => #<Lobster:0x0000010095aa50>
lobster.pacified_with_polar_bear?
# => true
jelly = JellyFish.new
# => #<JellyFish:0x00000100951108>
jelly.pacified_with_polar_bear?
# NoMethodError: undefined method `pacified_with_polar_bear?' for #<JellyFish:0x00000100951108>
class << jelly
extend Pacifiable
pacified_with :polar_bear
end
# => #<Proc:0x0000010093ddd8#(irb):4 (lambda)>
jelly.pacified_with_polar_bear?
# => true
big_jelly = JellyFish.new
# => #<JellyFish:0x0000010091ad38>
big_jelly.pacified_with_polar_bear?
# NoMethodError: undefined method `pacified_with_polar_bear?' for #<JellyFish:0x0000010091ad38>
From what I know of Ruby classes, once I include a module into a
class, ... but not retroactively alter any existing instances
of User.
On the contrary, an include/extend immediately affects all existing instances, because it is a question of pointer between the class and its superclass. See How does Inheritance work in Ruby? and also the links inside.
module HasAnAvatar
def m
puts 'in HasAnAvatar#m'
end
end
class AvatarUploader; end
class User
end
user = User.new
print 'user.respond_to?(:m) ? '; puts user.respond_to?(:m) ? 'yes' : 'no'
print '1) user.respond_to?(:avatar) ? '; puts user.respond_to?(:avatar) ? 'yes' : 'no'
class User
include HasAnAvatar
end
print 'user.respond_to?(:m) ? '; puts user.respond_to?(:m) ? 'yes' : 'no'
print '2) user.respond_to?(:avatar) ? '; puts user.respond_to?(:avatar) ? 'yes' : 'no'
module HasAnAvatar
def self.included(base)
puts "#{self} included into #{base}"
# base.mount_uploader :avatar, AvatarUploader
base.send(:attr_reader, :avatar)
end
end
class User
include HasAnAvatar
end
print '3) user.respond_to?(:avatar) ? '; puts user.respond_to?(:avatar) ? 'yes' : 'no'
print 'user.avatar : '; p user.avatar
print 'user.avatar.class : '; p user.avatar.class
user.instance_variable_set(:#avatar, AvatarUploader.new)
print 'after set, user.avatar : '; p user.avatar
print 'user.avatar.class : '; p user.avatar.class
Execution (Ruby 1.9.2) :
$ ruby -w t.rb
user.respond_to?(:m) ? no
1) user.respond_to?(:avatar) ? no
user.respond_to?(:m) ? yes
2) user.respond_to?(:avatar) ? no
HasAnAvatar included into User
3) user.respond_to?(:avatar) ? yes
user.avatar : nil
user.avatar.class : NilClass
after set, user.avatar : #<AvatarUploader:0x007fcc2b047cf8>
user.avatar.class : AvatarUploader
So included methods immediately become available to all existing instances.
Why does user.avatar answer nil ? Because instance variables belong to ... single instances. See Why are symbols in Ruby not thought of as a type of variable?
In this case, the old user was not assigned an avatar.
But why do you get user2.avatar.class #=> AvatarUploader. I suppose that, behind the scene, base.mount_uploader :avatar, AvatarUploader does something such that :avatar is not an accessor of a corresponding instance variable, or defines an initialize method which starts to set this variable into new instances.
Here is a solution for the second example :
module Pacifiable
def self.extended(host)
puts "#{host} extended by #{self}"
def host.pacified_with(mechanism)
##method_name = "pacified_with_#{mechanism}?"
puts "about to define '#{##method_name}' into #{self} of class #{self.class }"
if self.class == Class
then # define an instance method in a class
define_method(##method_name) { true }
else # define a singleton method for an object
class << self
define_method(##method_name) { true }
end
end
end
end
end
class JellyFish
define_method(:is_squishy?) { true }
end
class Lobster
extend Pacifiable
pacified_with :polar_bear
define_method(:is_squishy?) { false }
end
a_lobster = Lobster.new
print 'a_lobster.pacified_with_polar_bear? '; p a_lobster.pacified_with_polar_bear?
print 'a_lobster.is_squishy? '; p a_lobster.is_squishy?
jelly = JellyFish.new
### Add functionality to instance
#
## what I want:
#
jelly.extend(Pacifiable)
jelly.pacified_with(:polar_bear)
print 'jelly.pacified_with_polar_bear? '; p jelly.pacified_with_polar_bear? #=> true
Execution :
Lobster extended by Pacifiable
about to define 'pacified_with_polar_bear?' into Lobster of class Class
a_lobster.pacified_with_polar_bear? true
a_lobster.is_squishy? false
#<JellyFish:0x007fcc2b047640> extended by Pacifiable
about to define 'pacified_with_polar_bear?' into #<JellyFish:0x007fcc2b047640> of class JellyFish
jelly.pacified_with_polar_bear? true
About jelly.class.pacified_with(:polar_bear) : the mistake was to call class, which answers the class JellyFish; what you want is the singleton class of the instance object jelly. Once a method is defined in the singleton class of an object, you can send it directly to the object. The same applies to classes, which are instances of Class. Once a method is defined in the singleton class of a class, you can send it directly to the class. We call them class methods, they are actually instance methods of the singleton class of the class. Ouf !
Last OR : as explained, if you extend the class, it is valid for all existing instances. Thus you have to extend individual instances :
class JellyFromTheBigBlueSea
def find
puts 'in JellyFromTheBigBlueSea#find'
jelly = JellyFish.new
jelly.extend(Pacifiable)
jelly.pacified_with :polar_bear
jelly
end
end
class JellyFromAnIsolatedCove
def find
puts 'in JellyFromAnIsolatedCove#find'
JellyFish.new
end
end
normal_jelly = JellyFromTheBigBlueSea.new.find
ignorant_jelly = JellyFromAnIsolatedCove.new.find
## what I want:
#
print 'normal_jelly.pacified_with_polar_bear? '; p normal_jelly.pacified_with_polar_bear?
print 'ignorant_jelly.pacified_with_polar_bear?' ; p ignorant_jelly.pacified_with_polar_bear?
Execution :
in JellyFromTheBigBlueSea#find
#<JellyFish:0x007fb5d9045060> extended by Pacifiable
about to define 'pacified_with_polar_bear?' into #<JellyFish:0x007fb5d9045060> of class JellyFish
in JellyFromAnIsolatedCove#find
normal_jelly.pacified_with_polar_bear? true
ignorant_jelly.pacified_with_polar_bear?t.rb:109:in `<main>': undefined method `pacified_with_polar_bear?' for #<JellyFish:0x007fb5d9044d18> (NoMethodError)
First of all, the way with instance extend calls is working (See end of the post).
Then you should take into account some opinions that this is bad for performance
And at last, according to sources, it should work as expected. So I suggest you to wear your debug hat and try to see what exactly happens on user.extend part. For example, see if mount_uploader uses Carrierwave::Mount#mount_uploader method, as there are defined 'Uploader' everrides.
1.9.3p327 :001 > class A
1.9.3p327 :002?> def foo
1.9.3p327 :003?> '42'
1.9.3p327 :004?> end
1.9.3p327 :005?> end
=> nil
1.9.3p327 :006 > A.new.foo
=> "42"
1.9.3p327 :011 > module Ext
1.9.3p327 :012?> def foo
1.9.3p327 :013?> 'ext'
1.9.3p327 :014?> end
1.9.3p327 :015?> end
=> nil
1.9.3p327 :016 > class AFancy
1.9.3p327 :017?> def call
1.9.3p327 :018?> a = A.new
1.9.3p327 :019?> a.extend Ext
1.9.3p327 :020?> a
1.9.3p327 :021?> end
1.9.3p327 :022?> end
=> nil
1.9.3p327 :023 > a1 = A.new
=> #<A:0x00000000e09b10>
1.9.3p327 :024 > a2 = AFancy.new.call
=> #<A:0x00000000e17210>
1.9.3p327 :025 > a3 = A.new
=> #<A:0x00000000e1bd38>
1.9.3p327 :026 > [a1, a2, a3].map(&:foo)
=> ["42", "ext", "42"]
Ok, I think I found out what was causing my issues...
In https://github.com/jnicklas/carrierwave/blob/master/lib/carrierwave/mount.rb, in the definition of CarrierWave::Mount::Mounter, there are three references to record.class. This rightly references back to the main User class, which doesn't have the extended methods I loaded into the user's metaclass. So, I changed those to this: https://gist.github.com/4465172, and it seemed to work.
Also seems to continue to work if used normally like in the CarrierWave docs, so that's good too. Will continue testing it, though.
Firstly there's something odd about your contexts or at least the naming. Context do not return RolePlayers. Roles exist only inside a context. The role methods are not and should not be accessible outside the context. That said the standard way of dealing with DCI in ruby is method injection. This approach is not flawless but is so far the closest to pure DCI anyone have come in Ruby. There's an experimental library called alias_dci that might help.
EDIT
There now is a gem that makes injectionless DCI possible in Ruby. It's based on the work gone into Marvin, the first language to support injectionless DCI. The gem is called Moby and can be installed with
gem install Moby
it's currently still somewhat experimental but the smoke test of being able to implement the DCI examples from fullOO has been passed

What's the difference between defining class instance variable in a class and a method. Why?

Well, like the title shown, what's the difference between the two class instance variables below
class Document
#default_font = :Arial
...
end
And
class Document
def foo
#default_font = :Arial
end
...
end
Is there anyone can explain it to me. Thank you very much.
The second scenario isn't a class instance variable. It's a plain old instance variable.
In your first case, the variable is neither a class variable(which should have started with ##, nor an instance variable. It is simply a local variable not available outside the current scope, not even within the instance methods.
The second is an instance variable.
class Document
attr_accessor :var1, :var2
#var1 = 1
puts #var1 + 2
def initialize
#var2 = 4
puts #var2**2
#puts #var1 + 6
end
end
1.9.2p0 :208 > class Document
1.9.2p0 :209?> attr_accessor :var1, :var2
1.9.2p0 :210?> #var1 = 1
1.9.2p0 :211?> puts #var1 + 2
1.9.2p0 :212?>
1.9.2p0 :213 > def initialize
1.9.2p0 :214?> #var2 = 4
1.9.2p0 :215?> puts #var2**2
1.9.2p0 :216?> #puts #var1 + 6
1.9.2p0 :217 > end
1.9.2p0 :218?>
1.9.2p0 :219 > end
3
=> nil
1.9.2p0 :220 > d = Document.new
16
=> #<Document:0x1a2f0c #var2=4>
The #var1 + 6 inside the instance method gives an error.
I didn't see a final answer to the original post. I recently read the book "Eloquent Ruby" by Russ Olsen, so decided to add my take-away here.
The scope in which the first example defines the single-# variable is a class-scope, i.e., if the keyword self were to appear outside a method def but still within the class definition, it would reference the object Document, which is in turn an instance of the Class class.
The first example therefore defines a class instance variable, i.e., the variable #default_font is an instance variable of the object Document, a class object.
This class instance variable should only be accessed via the class level accessor methods, e.g., Document.default_font, rather than say, an_instance_of_Document.default_font
So if you define the attr_accessors:
class Document
#default_font = :Arial # this is a class instance variable,
# def self.default_font
# #default_font
# end
#
# def self.default_font=(font)
# #default_font = font
# end
# or replace the above two class methods with the following
class << self
attr_accessor :default_font
end
end
Here's some irb output:
1.9.3p429 :002 > require './document.rb'
=> true
1.9.3p429 :004 > Document.default_font
=> :Arial
1.9.3p429 :005 > Document.default_font = :Times
=> :Times
1.9.3p429 :006 > Document.default_font
=> :Times
1.9.3p429 :007 > doc = Document.new
=> #<Document:0x007fe6730d8228>
1.9.3p429 :008 > doc.default_font
NoMethodError: undefined method `default_font' for #<Document:0x007fe6730d8228>
The fact that the second example in the original post does not contain a single-# variable definition outside a method def, indicates that the #default_font here is an instance variable, which can only be referenced by an instantiated object of class Document.
class Document
def default_font=(font)
#default_font = font # this defines and set an instance variable
end
def default_font
#default_font # this defines an instance variable
end
# or above two defs can be replaced by the following
attr_accessor :default_font # this defines the instance variable and access methods
end
Here's some irb output:
1.9.3p429 :001 > require './document.rb'
=> true
1.9.3p429 :002 > Document.default_font
NoMethodError: undefined method `default_font' for Document:Class
1.9.3p429 :003 > doc = Document.new
=> #<Document:0x007ffbf1136f88>
1.9.3p429 :004 > doc.default_font
=> nil
1.9.3p429 :005 > doc.default_font = :Arial
=> :Arial
1.9.3p429 :006 > doc.default_font
=> :Arial

Resources