3

In Rails we can define a class like:

class Test < ActiveRecord::Base
  before_initialize :method
end

and when calling Test.new, method() will be called on the instance. I'm trying to learn more about Ruby and class methods like this, but I'm having trouble trying to implement this in plain Ruby.

Here's what I have so far:

class LameAR
  def self.before_initialize(*args, &block)
    # somehow store the symbols or block to be called on init
  end

  def new(*args)
    ## Call methods/blocks here
    super(*args)
  end
end

class Tester < LameAR
  before_initialize :do_stuff

  def do_stuff
    puts "DOING STUFF!!"
  end
end

I'm trying to figure out where to store the blocks in self.before_initialize. I originally tried an instance variable like @before_init_methods, but that instance variable wouldn't exist in memory at that point, so I couldn't store or retrieve from it. I'm not sure how/where could I store these blocks/procs/symbols during the class definition, to later be called inside of new.

How could I implement this? (Either having before_initialize take a block/proc/list of symbols, I don't mind at this point, just trying to understand the concept)

3
  • Your question is a little bit confusing. What do you mean by "an instance method like @before_init_methods"? @before_init_methods is an instance variable, not a method. Also, what do you mean by "that instance method wouldn't exist in memory at that point, so I couldn't store or retrieve from it."? Instance methods exist as soon as they are defined, instance variables exist as soon as they are assigned the first time. In fact, an instance variable is exactly the right solution to your problem, so I am a little bit confused why you are so opposed to that. Commented Jul 1, 2018 at 11:20
  • @JörgWMittag I have editted the question. I meant to say "instance variable", not "instance method". The instance variable would not exist until an instance is created, so it wouldn't be able to add stuff to it during the class definition. Commented Jul 1, 2018 at 11:28
  • You can check my answer which uses an instance variable without any problems. Commented Jul 1, 2018 at 11:32

2 Answers 2

4

For a comprehensive description, you can always check the Rails source; it is itself implemented in 'plain Ruby', after all. (But it handles lots of edge cases, so it's not great for getting a quick overview.)

The quick version is:

module MyCallbacks
  def self.included(klass)
    klass.extend(ClassMethods) # we don't have ActiveSupport::Concern either
  end

  module ClassMethods
    def initialize_callbacks
      @callbacks ||= []
    end

    def before_initialize(&block)
      initialize_callbacks << block
    end
  end

  def initialize(*)
    self.class.initialize_callbacks.each do |callback|
      instance_eval(&callback)
    end

    super
  end
end

class Tester
  include MyCallbacks
  before_initialize { puts "hello world" }
end

Tester.new

Left to the reader:

  • arguments
  • calling methods by name
  • inheritance
  • callbacks aborting a call and supplying the return value
  • "around" callbacks that wrap the original invocation
  • conditional callbacks (:if / :unless)
  • subclasses selectively overriding/skipping callbacks
  • inserting new callbacks elsewhere in the sequence

... but eliding all of those is what [hopefully] makes this implementation more approachable.

Sign up to request clarification or add additional context in comments.

Comments

2

One way would be by overriding Class#new:

class LameAR
  def self.before_initialize(*symbols_or_callables, &block)
    @before_init_methods ||= []
    @before_init_methods.concat(symbols_or_callables)
    @before_init_methods << block if block
    nil
  end

  def self.new(*args, &block)
    obj = allocate

    @before_init_methods.each do |symbol_or_callable|
      if symbol_or_callable.is_a?(Symbol)
        obj.public_send(symbol_or_callable)
      else
        symbol_or_callable.(obj)
      end
    end

    obj.__send__(:initialize, *args, &block)
  end
end

class Tester < LameAR
  before_initialize :do_stuff

  def do_stuff
    puts "DOING STUFF!!"
  end
end

3 Comments

This works as I want it to, thanks! Though I have a question about something I find confusing, which is the Procs here. If I create a proc like this proc = Proc.new { puts "Hi!" }, then I have to call it like proc[], or proc.call(), however when it's added via before_initialize, then called in self.new, it can be called like proc.(obj). Why is this? It's still a proc, yet in this case it can be called in a way that is usually can't.
I don't understand your question. proc.() is simply syntactic sugar for proc.call(), just like proc[] is syntactic sugar for proc.[]() or -a + b is syntactic sugar for a.-@().+(b). Note that this has nothing to do with procs. It is just syntactic sugar, it doesn't care what the receiver is. It will simply result in a send of the call message and if the object responds to it, then fine, otherwise you get a NoMethodError. What do you mean by "called in a way that it usually can't?"
BTW: What this code does (minus the middle part, of course), is also exactly what Class#new does by default.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.