← Back to Upcase

Implementing the Page Object Pattern, reference controller?


(Justin Gordon) #1

I’m looking for advice on whether PageObjects should have a reference to the controller, or to objects gathered from the controller, such as the current_user?

I’m using MobileFu, and the code presented below seems to put the methods needed on the controller.

Should I avoid passing the controller and create an object called MobileInfo with the mobile methods needed. This object would simply delegate the needed mobile methods to the controller.

I suspect that avoiding passing the controller to the PageObject clarifies the dependencies of the PageObject, and thus would make it more testable.

Code that Passes Controller to PageObject

# Page object for home page
class Pages::HomePo < BasePo
  def initialize(current_user, params, request, controller)
    super(current_user, params, request, controller)
  end

  def some_image
    @controller.is_mobile_device? ? "smallimage" : "bigimage"
  end
end

and

class BasePo
  attr_reader :current_user, :params, :request

  def initialize(current_user, params, request, controller)
    @current_user = current_user
    @params = params
    @request = request
    @controller = controller
  end
end

MobileFu snippet:

module ActionController
  module MobileFu

    def self.included(base)
      base.extend ClassMethods
    end

    module ClassMethods
      # Add this to one of your controllers to use MobileFu.
      #
      #    class ApplicationController < ActionController::Base
      #      has_mobile_fu
      #    end
      #
      def has_mobile_fu(set_request_format = true)
        include ActionController::MobileFu::InstanceMethods

        before_filter :set_request_format if set_request_format

        helper_method :is_mobile_device?
        helper_method :is_tablet_device?
        helper_method :in_mobile_view?
        helper_method :in_tablet_view?
        helper_method :is_device?
        helper_method :mobile_device
      end

(Greg Lazarev) #2

It’s a good idea to identify just the things that each Page Object might need, and pass in only the things that are needed. For example, in your HomePo you pass in the current_user, params and request but don’t actually use it. I’d say just pass in @controller.is_mobile_device?.


(Justin Gordon) #3

The HomePo had a few more methods that I excluded to shorten the code.

Suppose most subclasses of BasePo do use params, current_user, and request. Should HomePo just pass nil to the superclass?

Or given that’s going to be a number of commonly used utility methods on BasePo, would it seem reasonable, for the consistency argument, to include these common params?


(Greg Lazarev) #4

It would help if your question was a bit more focused. I’m not sure why you have BasePo at all. Does it have some functionality that is shared across several Page Objects (that I can’t see from the paste above)? If not, then you probably don’t need BasePo to start with. As for Pages::HomePo, I would only pass in things that it needs, and avoid passing all of the request and controller. Those are big objects, and unless you’ll be making 3 or 4 method calls on each, pass in only those values that you’ll need.

Not sure what exactly you mean by this. You either want to override the initializer, or not define it at all, on HomePo. This is how inheritance works in Ruby:

class Animal
  attr_reader :name

  def initialize(name)
    @name = name
  end
end


class Dog < Animal
end

puts Dog.new('Fluffy').name

(Justin Gordon) #5

Hi Greg. I omitted several utility methods taken out of the ApplicationController and ApplicationHelper that I put into the BasePo, which would need the current_user, params, and request. The methods are in the BasePo because a number of the methods in the subclasses depend on those utility methods. My main question was whether or not a Page Object should or should not reference a controller, which you answered above. I believe including a reference to the controller would tend to break the “Law of Demeter”.


(Justin Gordon) #6

The deeper question is whether or not all “Page Objects” should have a reference to the context of the current_user, params, and request, or whether these should get passed into the page objects as needed.

Given that I’m using Draper, I’ve changed my strategy for my “page objects” to be consistent with how Draper works. So instead of instantiating page objects with anything available in the Draper helper object, I simply include this in any page objects for which I’ll want the helpers:

include Draper::ViewHelpers

and then I can access the helper with h, such as h.params or h.current_user or h.request.

The end result is that:

  1. I eliminate a base class that wasn’t pulling it’s weight.
  2. I moved some helper methods that need to be accessed from controllers and views to ApplicationHelper
  3. Construction of Page Objects is much simpler, passing only the values needed.
  4. Page Objects work similarly my Draper decorators.

Well, why not just use the Draper decorator? Why have a page object? My reason is that the decorator for this model has already grown quite large. I want a number of methods that are only related to the connection between the controller and a specific view. Does this make sense? So I keep functionality across many views in the Draper decorator and I place methods that apply to only a specific controller-action-view inside of what I’m calling a Page Object.