Extending controllers and models of Rails 3.2+ Engines

For extending models (and controller methods) I use concerns:

https://github.com/schneems/wicked/tree/master/lib/wicked/controller/concerns

Then you can include them in other classes or modules in your repo.

https://github.com/schneems/wicked/blob/master/lib/wicked/wizard.rb

Then you can let your user know to add an include statement in the readme.

https://github.com/schneems/wicked

class AfterSignupController < ApplicationController
    include Wicked::Wizard

Some people like to automatically add methods to ActiveRecord::Base or other similar classes, this allows them to have a dsl like acts_as_tree but this just pollutes the available methods, and makes me have to remember unneeded dsl when we ruby already has this type of behavior included with include

If you want to add methods directly to the ApplicationController of an app you can add a application_controller_helper.rb

https://github.com/schneems/opro/tree/master/lib/opro/controllers

You need to include it

require 'opro/controllers/application_controller_helper'

then you can define a helper method for it:

def self.include_helpers(scope)

ActiveSupport.on_load(:action_controller) do

include scope::ApplicationControllerHelper if defined?(scope::ApplicationControllerHelper)

end

end

and finally in your engine:

initializer “opro.include_helpers” do

Opro.include_helpers(Opro::Controllers)

end

For extending controllers like devise i’ve done this:

https://github.com/schneems/opro/blob/master/lib/opro/rails/routes.rb

You use the user supplied controller or fall back to a default view.

Digging in the devise source as well can be tremendously valuable, though slightly daunting the first time or two. Let me know if you have some questions.

There has been some work done on the edgeguides around this as well. There are a few notes scattered throughout but I believe this section is that you are looking for.

http://edgeguides.rubyonrails.org/engines.html#using-a-class-provided-by-the-application

If that feels incomplete or lacking, please do contribute to make it better.

Mark McSpadden

We’re currently discussing the best way to do this on Forem’s issue #260 (https://github.com/radar/forem/pull/260). Kunal there wants to add methods to or modify the Forem::Post class, and so we’re going to go with the app/decorators directory for that.

Thanks Richard for the examples. From this it seems like you don’t monkey patching (Module::Class.class_eval).

Some people like to automatically add methods to ActiveRecord::Base or other similar classes, this allows them to have a dsl like acts_as_tree but this just pollutes the available methods, and makes me have to remember unneeded dsl when we ruby already has this type of behavior included with include

What’s the benefit and/or difference between

The first, reopening a class and defining methods, just adds new methods by adding them to the class. If there already was a method of the same name, it is redefined.

The later, including a concern in an existing class, adds methods by including a module containing them. This module is inserted into the inheritance chain just above the class itself but before any superclasses and previously included modules. Therefore, methods defined in such a module override those in the inheritance chain, but can access them through super. However, methods defined in the including class itself take precedence over those in any included module.

As such, IMO, the best way to ensure extensibility of a class is to define its functionality in modules and have the class itself contain barely more that a list of includes. That way, extensions can be included from new modules while still keeping the original functionality accessible to them. In particular, this avoids renaming methods with alias_method_chain or similar.

Michael

I decided to go the ActiveSupport::Concern strategy.

The reason is that a co-worker said he had done the Class.class_eval strategy

suggested by Forem/Spree (see Ryan Bigg’s comment), and this would randomly

break. The basic ActiveSupport::Concern strategy looks like this.

https://gist.github.com/3132025

Currently the dummy app is functional and rspec can run a simple test.

https://github.com/thelabtech/questionnaire/commit/20239148549f9aa8dafc6d76435de6614a2a9753

I’ll continue working on the gem (Questionnaire, :branch => activesupport) and

will be folding it into a Rails applicaiton (ccc360, :branch => rails32).

It seems pretty useful for other engines as well! Any chances to be included in Rails?

Added 2 strategies for extending Engine Models. Feedback is welcome.

https://github.com/lifo/docrails/commit/890b9dd4439986e306ec4fc0067a00effa606204

I found the time to package the decorator pattern into a gem:

Hope it is helpful.

What if the engine’s implementation was as generic as possible and relied on having an API like approach in which the app could redefine behaviour by changing just the implementation file. Here’s an example:

Engine

class UserController < Engine::ApplicationController

def create

@user.confirm_subscription

end

end

class User < ActiveRecord::Base

include Engine::UserSubscriptions

end

module Engine

module UserSubscriptions

def confirm_susbscription

blah

end

end

end

App

Rewrite the User Subscriptions module not the class itself.

Maybe the example is not the best, but what do you think of this approach of having all the behaviour you want to expose in mixins and seeing changing the models or controllers as a bad smell. Does it make sense?

Hi Luís,

The possible cases the engine's implementation should cover are huge

and indeterminable. Besides, sometimes you should wait for the engine’s developers to accept or implement required changes.

The decorator pattern provides a handy, direct and neat mechanism to

achieve the stuff.

Yes, I understand that it is a much more flexible solution as it allows the developer to change anything. I’m just not sure that hacking away the engine is such a good policy, because if the developer does not understand the engine’s code base well, he might break some things without even noticing, as he won’t be running the engine’s tests.

With the API aproach we kind of guarantee that it won’t break anything but the functionality your changing, as well as mantaining the relevant code all in the same place.

But maybe I’m just too paranoid. :stuck_out_tongue:

The decorator seems a great improvement on where it was before, though, don’t get me wrong.