Forum: Rails Engines lib folders and application controllers

253239ab19dbf1a2f4078b40f96ab555?d=identicon&s=25 Trey Bean (treybean)
on 2007-02-16 19:47
(Received via mailing list)
What's the best way to handle items in your engine's lib folder?  I have
a
couple of support files I would normally place in my lib folder and then
include in my application controller.  In developing my engine, I placed
these files in the lib folder within my engine.  Then, I did what I
would
normally do and added the include code to the application controller in
my
engine.  I quickly realized that this was being overwritten with the
surrounding application's application controller, and not wanting to
require
those applications to delete their application controller, I just pasted
the
code into the surrounding application controller (there might be a
better
way to do this, but one of the lines is a before_filter).

Anyway, that works when I actually run the application, but now, when I
run
my tests, I get an error saying:

`const_missing': uninitialized constant
ApplicationController::AuthenticatedSystem (NameError)

So, I guess my question is, what is the best practice regarding both
files
in your engine's lib folder as well as including code in the surrounding
application's application controller?

Thanks,
Trey
05d703f649ef1d07e78d7b479fb4c4ac?d=identicon&s=25 James Adam (Guest)
on 2007-02-20 11:00
(Received via mailing list)
I'm not 100% sure this is what you mean, but are you trying to include
a module into ApplicationController?

It sounds like you're creating an application controller class
*within* your plugin, which is not what you want to do. If you really
want to have the module included into ApplicationController
automatically whenever the plugin is loaded, try adding something like
this to your init.rb:

  ActionController::Base.send(:include, MyModule)

HTH,

James
253239ab19dbf1a2f4078b40f96ab555?d=identicon&s=25 Trey Bean (treybean)
on 2007-02-20 23:46
(Received via mailing list)
Indeed, this is exactly what I needed for including the module.  And I
loaded the before_filter with

ActionController::Base.send(:before_filter, :my_filter)

One question, where exactly in the flow does the init.rb come in?  I
mean,
could I also do

ApplicationController.send(:include, MyModule)  to be a little more
precise?  I tried and got a some errors about uninitialized constant
ApplicationController, so I'm guessing that class isn't loaded yet.

Oh well, what works works.  Is there any drawbacks to adding these to
the
ActionController::Base instead of the ApplicationController?  I realize
that
ApplicationController inherits from ActionController::Base (and thus it
works), I am just curious.

Trey
05d703f649ef1d07e78d7b479fb4c4ac?d=identicon&s=25 James Adam (Guest)
on 2007-02-21 10:51
(Received via mailing list)
At the point your plugins are loaded, it's best to assume that nothing
specific to your application is available - including
ApplicationController. Consider, for example, if ApplicationController
had references (includes, method invokations) which were provided by
plugins that are loaded *after* this one? It's definitely going to
crash and burn.

Another historical reason is that in development mode
ApplicationController was unloaded and then reloaded for each request.
This would mean that any additions you'd made to the class would be
lost after the first time you hit your development server.
ActionController::Base is a part of the framework, and so isn't
reloaded since it's never expected to change.

HTH

James
253239ab19dbf1a2f4078b40f96ab555?d=identicon&s=25 Trey Bean (treybean)
on 2007-02-21 23:44
(Received via mailing list)
That makes sense.  It just feels a little weird sending something to
ActionController::Base (I'd rather programatically add these lines to
the
surrounding app's application controller), but I think I can get over
it.

One further question though:

I mentioned in my last email, that I did the same thing with a
before_filter
and put the following in my init.rb

ActionController::Base.send(:before_filter, :my_filter)

This works when I run the site with script/server, but if I run my tests
with rake test:plugins PLUGIN=my_plugin, it doesn't invoke the
before_filter.  I've placed a puts line at the top of the my_filter
method,
so I can see when it's called, and through scirpt/server it is called
with
every request, as expected, but through the tests, it never gets called.

The odd thing is that I've verified that init.rb is getting called and
that
my other line in init.rb, ActionController::Base.send(:include,
MyModule) is
being loaded.  Should I not be doing this with before filters?  Or
should I
be doing something else in the test to make sure the before_filter is
set up
correctly.

Trey
58a68672882676f6bdf817b3c8ff3880?d=identicon&s=25 Tony Arcieri (Guest)
on 2007-02-21 23:51
(Received via mailing list)
The approach I've used is to place the relevant code in the my_engine.rb
module under lib, then include MyEngine in application.rb

That approach has worked fairly well for me.

- Tony
5f79cc51f2e2e7b2bb982134e9c649e0?d=identicon&s=25 Jeroen van Doorn (Guest)
on 2007-02-27 15:01
(Received via mailing list)
We're experiencing some problems which look like the earlier mentioned
ones.

Somehow the relations within our models are gone after one request. This
isn't the case when we are in production mode, so it has something to do
with loading.
In the init.rb we are extending the ActionController::Base by using
"ActionController::Base.class_eval do"

if we use send on calls to ActiveRecord like permission.send(:find,
:all) it only works on the first call. As soon as we reload the page, it
breaks. Again in production mode it just works.

We will be wading through the rails core and post back if we find
something.
Help is greatly appreciated.

Kind regards,
Jeroen van Doorn
73f812133e01dac8eb9b67746fe27393?d=identicon&s=25 Tekin Suleyman (tekin)
on 2007-02-27 15:37
(Received via mailing list)
I have a similar problem with certain calls in my init.rb only being
picked up on the first request..

It appears to me like init.rb is only getting run once during start-up.
This would explain why the mixins disappear after the first request -
when the entire environment gets reloaded, anything that was added in
init.rb will not get added the second time!

Having said that, other plugins are still being loaded and mixed in
correctly on subsequent requests... I have a feeling it's to do with the
way engines enhanced plugins are loaded but I'm not entirely sure how..

Tekin
5f79cc51f2e2e7b2bb982134e9c649e0?d=identicon&s=25 Jeroen van Doorn (Guest)
on 2007-02-27 16:00
(Received via mailing list)
We have a workarround, but we think it's quite dirty!

you could just add an empty model file to your apps model dir and just
require the original model.
In a test engine we don't have an init.rb and the relations work
properly over there.

Keep you posted.

Jeroen


Tekin Suleyman schreef:
73f812133e01dac8eb9b67746fe27393?d=identicon&s=25 Tekin Suleyman (tekin)
on 2007-02-27 16:22
(Received via mailing list)
Do you think this may be related to the recent changes to the way
dependencies are reloaded?

http://weblog.rubyonrails.org/2006/8/11/reloading-revamped/

Also, I came across:

Dependencies.log_activity = true

In my case, I am directly accessing, and modifying another plugin's
model in an init.rb.

>From a quick scan of the dependency log, it looks like my model is
getting reloaded during each request, which is happening after
initialisation and thus overriding anything in the init.rb

It looks like engines reloads all plugins after initialisation to add
the engines enhancements, which would explain why modifications are
picked up for the first request, as it happens after the app has been
loaded.

I guess the neat fix would be to get engines to reload after each
request and not just in after_initialize()
73f812133e01dac8eb9b67746fe27393?d=identicon&s=25 Tekin Suleyman (tekin)
on 2007-02-27 16:27
(Received via mailing list)
>> I guess the neat fix would be to get engines to reload after each
>> request and not just in after_initialize()

Or to not modify another plugin's model in an init.rb!!
5f79cc51f2e2e7b2bb982134e9c649e0?d=identicon&s=25 Jeroen van Doorn (Guest)
on 2007-02-27 16:46
(Received via mailing list)
We're really lost. I don't have clue where to look. We're still going
through rails and engines code, but we just don't get it at this moment.

We're not modifying other models b.t.w., but if we don't use a init.rb
at all it just works. A solution would be to put all the code in a lib
and include
it in the appcontroller, but I don't think this is a elegant solution.
Anyway I really want to understand why things are going wrong.

I'm going home, have a good night's sleep and have a look at it again
tomorrow.

@James Adam, do you have any clue on what we're doing wrong ?

Kind regards,
Jeroen van Doorn


Tekin Suleyman schreef:
05d703f649ef1d07e78d7b479fb4c4ac?d=identicon&s=25 James Adam (Guest)
on 2007-02-27 16:46
(Received via mailing list)
On 2/27/07, Tekin Suleyman <tekin@raid.nu> wrote:
> It appears to me like init.rb is only getting run once during start-up.

This is definitely the case, and is typically why you don't mix
modules from plugins into *anything* that will get reloaded. For
example, if you're adding methods to all controllers, you should
include your module into ActionController::Base, not
ApplicationController. The same applies for the rest of your
application...
73f812133e01dac8eb9b67746fe27393?d=identicon&s=25 Tekin Suleyman (tekin)
on 2007-02-27 17:22
(Received via mailing list)
Of course. I need to rethink my architecture, specifically how my
plugins integrate together.

On the most simple level, I have a cms plugin that provides a generic
cms, and other plugins will add additional functionality to it
(catalogue/ecommerce, etc).

I basically need to tell my Page model in the cms plugin that it has
additional behaviours. At the moment, I simply have a class attribute
array called @behaviours which the other plugins are adding to in their
init.rb's.

Obviously this is unworkable. Is there an obvious way to modify models
in other plugins in such a way that it get's picked up on reloads?

My current thinking is that maybe the Page model should check for and
load any enhancements into itself, looking in each plugin..
05d703f649ef1d07e78d7b479fb4c4ac?d=identicon&s=25 James Adam (Guest)
on 2007-02-27 17:32
(Received via mailing list)
On 2/27/07, Tekin Suleyman <tekin@raid.nu> wrote:
> Obviously this is unworkable. Is there an obvious way to modify models
> in other plugins in such a way that it get's picked up on reloads?

This is one of the occasions where you might actually want to adopt
"less magic". Is there any way you could architect it so each plugin
contributes a feature to a Page model within the application? I.E.

  class Page < AR::Base
    with_cms_features # from the cms plugin
    and_versioning # from your versioning plugin, for instance
    format_with_markdown # from your text processing plugin

    # ...

  end

The lesson learned from the login+user engine saga is: don't try to
have plugins depend on other plugins. It gets way too complicated. Is
there any way you can avoid this? I appreciate that this might really
be the advice you need at the moment; if I think of anything tonight
I'll fire of another mail to the list.
19ef12210a16c417cea94d115b933355?d=identicon&s=25 Brett Walker (Guest)
on 2007-02-27 17:41
(Received via mailing list)
Really?  You can't mix a module from a plugin into your app - you
have to add it into Rails??  That just seems somehow wrong to me.  I
thought I could mix a module in anywhere I want - if the main class
gets reloaded, why won't it's dependencies get reloaded as well?  Is
that in general, or only with engine plugins?

In any case, I'm running into the same problem with ActiveRbac.
First run, everything works.  Second run, methods for a model just
disappear.  In order to create a model that doesn't have to be
completely copied into the application to be overridden, they created
a model file that simply includes a mixin module with all the real
code.  So to override the model in my app I create the model file,
include the mixin files, and my new functions.

The problem seems to be in one of the models that I don't override.
First run everything is fine, second run *non* of it's methods are
found.  It's not just the mixed in ones.  I added a method straight
in to the model - it wasn't available on the second run.  Here is an
interesting error I get if I try to do a find on the model:

ArgumentError: A copy of ActiveRbacMixins::UserMixins::Core has been
removed from the module tree but is still active!

This worked fine in the previous Rails and Engines.  So this is new
behavior.  What's the best way to fix it?
73f812133e01dac8eb9b67746fe27393?d=identicon&s=25 Tekin Suleyman (tekin)
on 2007-02-27 17:54
(Received via mailing list)
I think my description may have not been very clear, there is infact
very little magic going on at all!

Each page has a behaviour attribute. For most pages, this is simply left
blank. If however, it is set to something, like for example, 'contact'
or 'news', when the page is rendered, extra 'stuff' is added to the
rendered page using partials.

This all works fine, all I want is for the page model to have a list of
potential behaviours - nothing more than an array of strings - which is
what I need to add to from future plugins.. Does that make sense?

I guess I could put this in the application somewhere, it just makes
sense for it to happen in the plugins.

Thanks for all the great work on engines by the way, it's made it much
easier for us to share rails code between our projects, the way it
should be.
73f812133e01dac8eb9b67746fe27393?d=identicon&s=25 Tekin Suleyman (tekin)
on 2007-02-27 18:08
(Received via mailing list)
Why create the model file at all in your app? Couldn't you simply open
the module and override/add your functions?
05d703f649ef1d07e78d7b479fb4c4ac?d=identicon&s=25 James Adam (Guest)
on 2007-02-27 18:09
(Received via mailing list)
On 2/27/07, Brett Walker <walkerbl@fastmail.us> wrote:
> Really?  You can't mix a module from a plugin into your app - you
> have to add it into Rails??  That just seems somehow wrong to me.  I
> thought I could mix a module in anywhere I want - if the main class
> gets reloaded, why won't it's dependencies get reloaded as well?  Is
> that in general, or only with engine plugins?

You're talking about a few things here. Imaging the situation:

app/models/a.rb:
  class A
  end

app/models/b.rb
  class B
    include C
  end

vendor/plugins/monkey/lib/c.rb
  module C
  end

vendor/plugins/monkey/init.rb:
  A.send(:include, C)

When your application first loads, init.rb is evaluated, and C is
included into A. C is also included into B, as per normal. At the end
of the request, A and B are dutifully unloaded to make way for any
changed versions. The next request causes Rails to try to find A
again, which it does in app/models/a.rb, and so it loads that file.
However, because vendor/plugins/monkey/init.rb is only ever evaluated
at the start of the request cycle, the "new" A will NOT include the
module C.

Since B explicitly refers to C, when Rails tries to reload B from
app/models/b.rb, it re-reads the class definition and includes C as
you'd expect.

The point here is that the only place where it states that A should
include C is a file that is only ever evaluated once, and that
information is lost when the target class is unloaded. This is what
plugin developers typically send includes to ActionController::Base,
not ApplicationController.

> In any case, I'm running into the same problem with ActiveRbac.
> First run, everything works.  Second run, methods for a model just
> disappear.  In order to create a model that doesn't have to be
> completely copied into the application to be overridden, they created
> a model file that simply includes a mixin module with all the real
> code.  So to override the model in my app I create the model file,
> include the mixin files, and my new functions.

That's basically what's going on with class B, above.

> The problem seems to be in one of the models that I don't override.
> First run everything is fine, second run *non* of it's methods are
> found.  It's not just the mixed in ones.

Just to be sure that I know what you're referring to:

* there is a model within a plugin
* it has, say, method "do_something" defined directly in the class
* on the second request, the model class is still available but the
method has disappeared

Is that right?

> I added a method straight
> in to the model - it wasn't available on the second run.  Here is an
> interesting error I get if I try to do a find on the model:
>
> ArgumentError: A copy of ActiveRbacMixins::UserMixins::Core has been
> removed from the module tree but is still active!

I can't speak for ActiveRbac specifically, but I've never, ever
encountered this error. Very weird.

> This worked fine in the previous Rails and Engines.  So this is new
> behavior.  What's the best way to fix it?

Since any version of ActiveRbac that was compatible with the engines
plugin 1.1.x releases isn't compatible with the 1.2 release, you
should also note that ActiveRbac must've changed.

If you can reproduce this in a clean project using the engines plugin
and a toy plugin to contain the model, please zip it up and email it
directly to me. I'm very keen to resolve this, but I need to be able
to reproduce the behaviour...

Cheers
73f812133e01dac8eb9b67746fe27393?d=identicon&s=25 Tekin Suleyman (tekin)
on 2007-02-27 18:10
(Received via mailing list)
Sorry, what I meant was; maybe having the second model file in your app
is what's causing the problem?

http://api.rails-engines.org/classes/Engines/Rails...
s.html


>> Why create the model file at all in your app? Couldn't you simply
>> open the module and override/add your functions?
19ef12210a16c417cea94d115b933355?d=identicon&s=25 Brett Walker (Guest)
on 2007-02-27 21:13
(Received via mailing list)
On Feb 27, 2007, at 10:39 PM, James Adam wrote:

>   class A
>
> module C.
>
> Since B explicitly refers to C, when Rails tries to reload B from
> app/models/b.rb, it re-reads the class definition and includes C as
> you'd expect.
>
> The point here is that the only place where it states that A should
> include C is a file that is only ever evaluated once, and that
> information is lost when the target class is unloaded. This is what
> plugin developers typically send includes to ActionController::Base,
> not ApplicationController.

Ok, I understand that I think.  The explicit include works, but
anything that might get executed from the init.rb will never get run
other than the first time.

So the plugin is never unloaded?  If it was, the init.rb would have
to be executed again.

But then I didn't understand your comment about the lesson learned
with Login+User engine.  Are you saying it's not possible/advisable
to have engines use services of other plugins/engines?  I can't have
a User engine that gets referenced by a Newsletter engine, for
example, or use the Liquid or Whitelist plugins from engine code?

>
>
> Is that right?

app/models/user.rb:
   class User
      Include Monkey::UserMixin
   end

vendor/plugins/monkey/model/user.rb
   class User
      Include Monkey::UserMixin
   end

vendor/plugins/monkey/model/group.rb
   class Group
      Include Monkey::GroupMixin

      def test
        true
      end
   end

vendor/plugins/monkey/lib/usermixin.rb
   module Monkey
     module UserMixin
       def self.included(base)
         base.class_eval do
           has_and_belongs_to_many :groups

           def all_roles
             for group in self.groups
               result << group.all_roles
             end
           end
         end
       end
     end
   end

vendor/plugins/monkey/lib/groupmixin.rb
   module Monkey
     module GroupMixin
       def self.included(base)
         base.class_eval do
           has_and_belongs_to_many :users

           def all_roles
              return true
           end
         end
       end
     end
   end

When I call user.all_roles for the second time, I get the

   NoMethodError (undefined method `all_roles' for #<Group>)

If I then do a Group.find(1) from a breakpoint, I get the

ArgumentError: A copy of ActiveRbacMixins::UserMixins::Core has been
removed from the module tree but is still active!

Also, it's not just methods that are mixed in that are hosed.
Group.test also is not available, that is defined directly in the class.

Could it be that on the second time through, the app version of user
is unloaded, and thus the corresponding mixin is unloaded.  But the
version of the model in the plugin is not unloaded, but mixin is
removed from the module.  So now the engine model is there, partially
stripped, and gets used instead of the reloaded user model in app.
Yes, I'm grasping at straws.

> encountered this error. Very weird.
> directly to me. I'm very keen to resolve this, but I need to be able
> to reproduce the behaviour...

Yes, ActiveRbac was modified to satisfy the new engine requirements
(not using config(), removing init_engine, etc).  No other logic
changes.

I will see if I can recreate it in toy plugin.

Thanks for your help,
Brett
5f79cc51f2e2e7b2bb982134e9c649e0?d=identicon&s=25 Jeroen van Doorn (Guest)
on 2007-03-01 08:58
(Received via mailing list)
Had a day of yesterday, but what James describes, exactly happens to us

> * there is a model within a plugin
> * it has, say, method "do_something" defined directly in the class
> * on the second request, the model class is still available but the
> method has disappeared

One of my colleages has found a sollution, but hasn't arived yet. I'll
post our findings as soon as he's in.

Regards,
Jeroen



James Adam schreef:
5f79cc51f2e2e7b2bb982134e9c649e0?d=identicon&s=25 Jeroen van Doorn (Guest)
on 2007-03-01 09:32
(Received via mailing list)
Hi All,

My colleage posted an entry on my blog about this problem and I didn't
even notice :)
http://www.jeroenvandoorn.nl/index.php/2007/02/28/...

Hope this helps.

Kind regards,
Jeroen van Doorn

Jeroen van Doorn schreef:
2c17b1c9b894c3fcdccec587f1b6ee31?d=identicon&s=25 Victor Grey (Guest)
on 2007-03-05 15:39
It is perhaps a little simple-minded, but this solved the problem for
me. In my application controller I put this:

def reinit_engines_in_dev
  if ENV['RAILS_ENV'] == 'development'
    Rails.plugins.each do |plgin|
      if File.basename(plgin.root) =~ /_engine/
        IO.read("#{RAILS_ROOT}/#{plgin.root}/init.rb").each do |line|
          eval(line) unless (line.blank? or (line =~ /^\s*\#/))
        end
      end
    end
  end
end

=vg
2c17b1c9b894c3fcdccec587f1b6ee31?d=identicon&s=25 Victor Grey (Guest)
on 2007-03-05 15:40
Oh, and of course:

before_filter :reinit_engines_in_dev
This topic is locked and can not be replied to.