Topic: include/extend in modules

Hey all,

Let's say an application contains a class called RestController. And this is inherited by each of the controllers (e.g. BookController > RestController), where RestController in turn inherits from ApplicationController. Now let's say RestController uses the include keyword to include modules (e.g. include RestControllerBase include RestControllerActions). Since a module could invoke methods defined not in the module itself but in the class that it will be mixed with, using the include and extend methods, we mix in the methods implemented in the restcontrollerbase and restcontrolleractions modules with the RestController class.

Now my question. I read that "Essentially, when you “include” a module in a class, the objects created by that class have all the methods of that module (instance methods). When you use “extend” instead, the methods apply to the class (class methods)."

So when I think of class method, I think of a method called directly on the class, so if we have class Book, then a class method of read would be called like this: Book.read.

So here is a real-world working example, where the method resource_model refers to the model associated with the current  controller being invoked. My question is how do the methods defined below actually work as class methods:

  module ClassMethods

    def resource_model(resource_model_name)
      @resource_model = Kernel.const_get resource_model_name
    end
 
    def resource_model_from_controller_name
      resource_model_name = self.name.gsub("Controller", "").singularize
      Kernel.const_get resource_model_name
    end
 
    def get_resource_model
      @resource_model ||= resource_model_from_controller_name
    end
  end

Thanks for any response.

Re: include/extend in modules

When you include a module into a class, the methods are added to the class as instance methods. When you extend the class with the module, the methods are added to the class's metaclass (or eigenklass) and act as singleton methods (or class methods)

Re: include/extend in modules

My problem understanding the above example is that the resource_model class method IS the model itself. So if the model constant is Student, then is what's going on here is that a Student class is dynamically created? The class itself here is the singulaton method? Or are we calling the Student class method on the Student class (Student.Student)? The idea of dynamically creating  a model makes sense when a controller is invoked. However, how is that a  class method? That's what I don't get. A class method is called on a class.

Last edited by johnmerlino (2010-04-26 10:12:17)

Re: include/extend in modules

What are you including/extending the ClassMethods module into? I'll need to see more of your code to be able to answer your question, I'm not sure how you're using this module.

Re: include/extend in modules

This is code involved:

students_controller.rb
class StudentsController < RestController

rest_controller.rb #All this contains is just a bunch of include statements. No other code.
class RestController < ApplicationController
  include RestControllerBase
  include RestControllerActions
  include RestControllerActionTemplates
end

rest_controller_base.rb
module RestControllerBase
  def self.included(base)
    base.send :extend, ClassMethods
    base.send :include, InstanceMethods
    base.helper_method :resource_model, :resource_name, :resource_label,
      :resource_collection_name, :resource_collection_label
  end
    module ClassMethods
    def resource_model(resource_model_name)
      @resource_model = Kernel.const_get resource_model_name
    end
 
    def resource_model_from_controller_name
      resource_model_name = self.name.gsub("Controller", "").singularize
      Kernel.const_get resource_model_name
    end
 
    def get_resource_model
      @resource_model ||= resource_model_from_controller_name
    end
  end

  module InstanceMethods
    def resource_model
      @resource_model ||= self.class.get_resource_model
    end
   
    def resource_name
      resource_model.name.underscore
    end

    def resource_label
      resource_name.humanize 
    end
   
    def resource_collection_name
      resource_model.name.tableize
    end

    def resource_collection_label
      resource_collection_name.humanize
    end

Last edited by johnmerlino (2010-04-26 13:45:16)

Re: include/extend in modules

I think this is probably more simple than you think -- there are no classes and models being constructed on the fly. Rather, when you use the #resource_model method in your StudentsController, it uses naming logic in the #resource_model_from_controller_name method to determine what class you want returned, and in this case returns the Student class. This lets you do this:

class StudentsController < RestController
def index
  @students = resource_model.all #=> returns Student.all
end
end

This is the naming convention magic that is then used in RestControllerActions so that it can write your RESTful actions will automatically work. Does that help?

Re: include/extend in modules

Yeah this helps. I think there's a little confusion as to why @resource_model is assigned multiple times but I'm getting there. Thanks.