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.

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

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

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:

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/

Yanni Mac 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