0

I have an isolated Rails Engine: Admin.

In that Engine I create Sites through a GUI.

in Engine:

module Admin
  class Site < ActiveRecord::Base
  end
end

In main app I inherit from the engine's Site in order to use it as a constant at the root level:

class Site < Admin::Site
end

I do this because it feels wrong to couple the Admin::Site in models, controllers and tests of the main app. But I guess there's some downsides to this approach and I also guess one could argue that the coupling is the same.

How can I delegate this in a better way than inheritance?

OR

Should I restructure my code and maybe put the Site class in a gem that both the main app and the Engine can use? What I would really like is an interface to the engine's class to reduce the entry points, and thus the coupling.

Sidenote, I have a total of 3-4 of these classes that resides in the engine but is used in the main app.

EDIT:

Maybe I should just wrap it like this:

class Site 
  def initialize(args = {})
    @klass = Admin::Site.new(args)    
  end

  def method_missing(method_name, *args, &block)
    @klass.send(method_name, *args, &block)
  end
end

Of course, then I could also narrow the interface of Site to only include what I need from Admin::Site in the main app.

Yeggeps
  • 2,055
  • 2
  • 25
  • 34

1 Answers1

1

Two possible solutions come to mind,
1) ActiveSupport::Conern
2) Open Classing

Given that you have isolated these 3-4 Models/Controllers in an Engine, one approach is to leverage the ActiveSupport::Concern to explicitly include your Engine's functionality in the MainApp.

More info here, Rails Docs on ActiveSupport::Concern

ActiveSupport::Concern example:

# Rails Engine
module SomeEngine
  module SomeController

    extend ActiveSupprt::Concern

    included do
      before_filter :some_before_filter
    end

    # regular instance methods
    def index 
      ...
    end

    protected
    def some_before_filter
      ....
    end

  end
end    

# MainApp
class SomeController < BaseController
  include SomeEngine::SomeController

  # rest of MainApp logic
end

Another common approach is to rewrite a Ruby class methods at run time ("open classing"). Spree does this by implementing the "decorator" pattern on for Engine model/contrller classes in the MainApp.

More info here, Spree's implementation of Decorators

"#class_eval do" example,

# in MainApp
SomeEngine::SomeController.class_eval do
  # logic added in the MainApp
end

I would also check out this RailsConf talk aout RailsEngnes,
http://confreaks.com/videos/863-railsconf2012-rails-engines-patterns

westonplatter
  • 1,475
  • 2
  • 19
  • 30
  • Thanks westyfresh, while both solutions is great and solves different issues, I don't like any of them for my problem. All 3-4 classes are used throughout the Engine as well as multiple places in the main app, not as modules but as classes, I could just move them into modules of course and then include that module in both the engine and the app but that doesn't really make sense to me. I also don't really wan't to add logic in the main app I just want to use the classes at multiple places and would like to have a single entry point, but maybe I should just reference it `Admin::Site` directly. – Yeggeps Dec 10 '12 at 21:48
  • Just to better understand you: you want the functionality of "Admin::Site" in the MainApp but with the "Site" namespace? If so, include seems like closest solution. But if you don't want that, then I don't know of other options. – westonplatter Dec 10 '12 at 22:33