Extending a Model that is in a plugin

I have several apps that share common base models which I am coding into
a plugin, but each app may need to add some additional code or override
some code in the plugin model.

One of the main differences is that I use multiple database servers in
alot of these apps, which is managed by the “use_db” plugin. I am
trying to figure out a way to use the “use_db” plugin without hard
coding it into the plugin class. I dont want to have to change the
plugin every time I install it in a new app.

Can anyone think of a way to create a class inside the rails directories
that would be able to extend the plugin model and also be able to
specify my “use_db” database… overriding the default rails database.
I would also need to be able to add new methods and override methods
contained in the plugin model. The model in the plugin contains lots of
associations, named_scopes, etc… so that stuff would still need to be
active in the extended model… so I dont think a module would work.

For those not familiar with “use_db” it just allows you to include a
prefix which is pulled from database.yml, and the model will use this
database. Example:

class Widget < ActiveRecord::Base
use_db :prefix => “slave2_”
end

this would use the database config “slave2_production” in database.yml
for that model if you are in prod mode.

I have tried using “extends” inside the class, creating a subclass,
etc… but nothing seems to give me the behavior I desire. Any ideas
guys? Thanks in advance!

I have the same issue. Have you been able to figure it out?

Thanks,
Maher

Maher Hawash wrote:

I have the same issue. Have you been able to figure it out?

Thanks,
Maher

I moved on to other work after I posted this. I will probably revisit
this week. I will let you know what I figure out. There has to be an
elegant way to do this. I really just need to learn more about the
object-oriented side of Ruby works… reading about it now.

Andy, thank you very much for the reply. If I can get the first
solution to work, I think I would prefer that. I have been playing
around with the code and I am getting errors saying my model class (in
rails dir) should define Widget. So is it even possible to use Widget
as the name in the plugin Base model and at the same time have a rails
model with the same name using it as a subclass?

class Widget < MyPlugin:: Widget::Base

I was thinking I could just rename the base class to BaseWidget, but
then rails is looking for that database name (base_widgets) when it
loads widget since its an activerecord subclass.

Thanks for your help!

Andy, thank you very much for the reply. If I can get the first
solution to work, I think I would prefer that. I have been playing
around with the code and I am getting errors saying my model class (in
rails dir) should define Widget.

This normally means that you have a file named “widget.rb” but don’t
define
a class called “Widget” in it. The class name/file name must match
(except
for the conventional conversion to camelcase from underscores) for Rails
to
be able to find/autoload it.

So is it even possible to use Widget
as the name in the plugin Base model and at the same time have a rails
model with the same name using it as a subclass?

Yes.

You could even use the same class name, Ruby let’s you re-open a class
and
add new methods to it or change existing ones. But you can certainly
have a
base class declared in your plugin and have a specific class inherit
from
that in your app.

class Widget < MyPlugin:: Widget::Base

I was thinking I could just rename the base class to BaseWidget, but
then rails is looking for that database name (base_widgets) when it
loads widget since its an activerecord subclass.

Oohhh that’s a good point. You may have to name your plugin class as
MyPlugin::Widget as I don’t think ActiveRecord supports namespaced
classes
to tables out of the box (I remember adding code to a Rails project a
while
ago because I WANTED it to do that, but it may have changed by now).

However, from a quick play it seems that ActiveRecord doesn’t look for
the
database table unless you actually use it, and you shouldn’t be using
your
BaseWidget class but a subclass of it (correctly named to match the
table).

Thanks for your help!

No worries, keep posting back with what you have and what’s going wrong
until it’s solved; I’m sure we’ll be able to get you running…

Cheers,

Andy

On 26 February 2010 17:35, Yanni M. [email protected] wrote:

I have several apps that share common base models which I am coding into
a plugin, but each app may need to add some additional code or override
some code in the plugin model.

OK, so let’s assume your base model is like this:

class MyPlugin:: Widget::Base < ActiveRecord::Base
… some code …
end

One of the main differences is that I use multiple database servers in
alot of these apps, which is managed by the “use_db” plugin. I am
trying to figure out a way to use the “use_db” plugin without hard
coding it into the plugin class. I dont want to have to change the
plugin every time I install it in a new app.

OK, so then in your app you declare a model as:

class Widget < MyPlugin:: Widget::Base
use_db :prefix => “slave2_”
end

Alternatively, if you want to declare a User class in your plugin and
have
your app use that if it’s found, why not just create an initializer in
your
application. As you can read here -
http://railsguts.com/initialization.html - plugins are loaded before
application initializers, so you could put a file in your
config/initializers folder that just does:

class Widget
use_db :prefix => “slave2_”
end

The Widget class could already have been defined from your plugin
initializer, so you’re then just re-opening the class and adding your
use_db.

Can anyone think of a way to create a class inside the rails directories

that would be able to extend the plugin model and also be able to
specify my “use_db” database… overriding the default rails database.
I would also need to be able to add new methods and override methods
contained in the plugin model. The model in the plugin contains lots of
associations, named_scopes, etc… so that stuff would still need to be
active in the extended model… so I dont think a module would work.

I’m not sure I follow you. However, you could do the above with the
config/initializers to open your defined plugin class and add your
functionality. If you want you could just do a simple require within
the
config/initializer/widget.rb file that loads the file from within
app/models
(which may be a better place for your modifications).

require Rails.root.join(“app”, “models”, “widget”)

The reason for this is that you can’t just re-open the class within
app/models/widget.rb as that file will only be opened if the class isn’t
defined (it’s an autoloading path, not an “open every file in this path
at
boot time” path), so you need to specifically load it from an
initializer/environment file.

I have tried using “extends” inside the class, creating a subclass,
etc… but nothing seems to give me the behavior I desire. Any ideas
guys?

If you can flesh out your requirement/desires a bit more fully I’ll
happily
work up a solution with you, but I’m going on guesswork a bit at the
moment
:slight_smile:

Cheers,

Andy

Additionally, you can include a default class in your plugin. This
would give you the model out of the box when you install the plugin, so
you don’t even have to create a model in /app/models/

#-----------------------------------------------------------

/my_rails_app/vendor/plugins/my_plugin/widget.rb

----------------------------------------------------------

class Widget < ActiveRecord::Base
include BaseWidget
end

Then if you have additional functionality you need to add (like use_db),
you just drop a class into /my_rails_app/app/models/ and it will use
that one instead of the default class that is contained in the plugin.

Andy, Thanks for all your responses. This helped me do some additional
research. I found something that works perfectly for my scenario.
Using mixin modules.

http://weblog.jamisbuck.org/2007/1/17/concerns-in-activerecord

I tried using mixins earlier today, but I was not able to use the
associations, named_scope, etc. But you can add self.included(base)
around all the AR associations, etc. and this will load them into your
model class.

Here is my final setup which is working well so far:

#-----------------------------------------------------------

/my_rails_app/vendor/plugins/my_plugin/lib/base_widget.rb

----------------------------------------------------------

module BaseWidget
def self.included(base)
base.named_scope :expensive, :conditions=>“price>100”
base.belongs_to :manufacturer
end

def formatted_price
“$”+self.price.to_s
end
end

#-----------------------------------------------------------

/my_rails_app/app/models/widget.rb

----------------------------------------------------------

class Widget < ActiveRecord::Base
include BaseWidget
use_db :prefix => “slave2_”
end

Yanni M. wrote:

Additionally, you can include a default class in your plugin. This
would give you the model out of the box when you install the plugin, so
you don’t even have to create a model in /app/models/

#-----------------------------------------------------------

/my_rails_app/vendor/plugins/my_plugin/widget.rb

----------------------------------------------------------

class Widget < ActiveRecord::Base
include BaseWidget
end

Then if you have additional functionality you need to add (like use_db),
you just drop a class into /my_rails_app/app/models/ and it will use
that one instead of the default class that is contained in the plugin.

One gotcha… rails gets confused in certain situations and will throw a
Dup error. You need to add “unloadable” to the top of the model class.

class Widget < ActiveRecord::Base
unloadable
include BaseWidget
end