ActiveRecord: private versus protected methods

Hi,

I'm considering subclassing ActiveRecord::Migrator to enhance the way it tracks the migration version number of a database. However there's a hurdle: it has private methods.

Private methods are a major stumbling block for anyone needing to sub-class something. If the public methods of the class call the private methods, you can't sub-class without replacing all the private methods (because otherwise they won't be available in the descendant class).

A much better approach is to declare methods as 'protected'. That offers a lot more flexibility:

* The methods are still protected from being called from outside the class.

* The class can be sub-classed, and it will still work.

Does anyone know: is there a compelling reason for using 'private' in this case? Are there any core developers listening? Can you make your methods 'protected' instead of 'private' please?

I really don't want to have to maintain my own fork of ActiveRecord::Migrator.

Thanks and regards, Urbanus http://goldberg.240gl.org

I’m considering subclassing ActiveRecord::Migrator to enhance the way it tracks the migration version number of a database. However there’s a hurdle: it has private methods.

Private methods are a major stumbling block for anyone needing to

sub-class something. If the public methods of the class call the private methods, you can’t sub-class without replacing all the private methods (because otherwise they won’t be available in the descendant class).

irb(main):005:0> class C; def bar() foo end; def foo() puts ‘foo’ end; private :foo end => C irb(main):006:0> class D < C; end => nil irb(main):007:0> D.new.foo NoMethodError: private method `foo’ called for #<D:0x2aaaab4eab10>

    from (irb):7

irb(main):008:0> D.new.bar foo => nil

A much better approach is to declare methods as ‘protected’. That offers a lot more flexibility:

  • The methods are still protected from being called from outside the class.

  • The class can be sub-classed, and it will still work.

Does anyone know: is there a compelling reason for using ‘private’ in this case? Are there any core developers listening? Can you make your methods ‘protected’ instead of ‘private’ please?

You need protected only if you wish the call the method from a subclass. The inherited public method is not affected. Please bring up specific examples (preferably as Trac tickets) and we will address them.

Best, jeremy

Hi Jeremy,

You need protected only if you wish the call the method from a subclass. The

Exactly. That's the issue.

inherited public method is not affected. Please bring up specific examples (preferably as Trac tickets) and we will address them.

I'm considering a fairly deep change to the way migrations work. I'm looking at adding a *namespace* to migrations, so when doing a migration you would specify the name and version number for the required migration.

This would allow you to deploy parts of Rails apps as a kind of plug-in. So when you migrate up or down, you can specify which part of the database you're referring to.

For example, you might have a Rails app called "portal" that provides all the core functionality of your website, into which you can add optional plug-ins such as "cms", "discussion_board", etc. Each of those plug-ins would have their own database requirements, in addition to the main "portal".

I'm also looking at dependencies between the parts, e.g. "cms" version 4 might depend on "portal" version 2 or above.

Rails migrations almost do what I need; except that you can't put different sorts of migrations into the one Rails database (AFAIK).

I could do all this using the existing migration classes as a basis if -- as discussed previously -- I could sub-class them and override their public methods. But I can't, because the public methods rely on private methods.

Thanks, Urbanus

Exactly. That's the issue.

It's not strictly true. Private Methods can't be called with a receiver, that's all. So

class Base   private   def foo     1   end end

class Derived < Base   def calls_foo     foo   end end

Derived.new.calls_foo will work perfectly.

> inherited public method is not affected. Please bring up specific examples > (preferably as Trac tickets) and we will address them.

I'm considering a fairly deep change to the way migrations work. I'm looking at adding a *namespace* to migrations, so when doing a migration you would specify the name and version number for the required migration.

This sounds a little like what the engines guys have done with migrations, perhaps you can investigate how they implemented it?

Thanks Koz, that was very helpful.

A couple of guys at a client of mine implemented this, and it involved opening up one method to the outside world to specify the name of the schema_info table (we used schema_info_<name> for the different parts). It was quite small and simple. They didn't subclass, but as Koz mentioned private methods don't necessarily preclude subclassing.

The other stuff you're talking about (schema chunk dependencies) would be trickier, but I'd be inclined to leave that to application-level dependencies (surely you're going to need code to go along with those schemas, so tying it all up into a single version number thing would be pretty clean).

- Matt

Urbanus, that is exactly what I’m doing right now. I’m packaging it as a generic, wide audience plugin called Migration Extensions. I haven’t been troubled by private methods, though. It’s a shame we’re building the same thing separately… Tell me if you’re interested to see, next week when I’m going to have something concrete in alpha state I can share it with you.

(Sorry for the delay in my reply: I've been burning the midnight oil on my own project.)

I'd definitely be interested in seeing what you've got. I haven't actually started anything: it's more of a future plan for me that I'm preparing for now.

If you're going to do this as a free software project I'd be willing to help. You can e-mail me directly if you like: urbanus at 240gl dot org.

Regards, Dave Nelson

wait, you CAN put diffferent sorts of migrations into ont Rails Database. through engine plugins. take the login_engine plugin as an example. it includes its own migration which are separate set from the applications migrations. each engine plugins can have their own. you might not need to extend migrator at all.

.schema engine_schema_info

CREATE TABLE engine_schema_info (engine_name varchar(255), version integer);

.schema schema_info

CREATE TABLE schema_info (version integer);

select * from schema_info;

14

select * from engine_schema_info;

login_engine|1

regards,

Yes. Michael pointed this out. The Engines guys seem to have the right approach IMHO. The idea (or at least MY idea) would be to have a similar mechanism but distinct from the whole Engines framework.

Regards, Dave

my question is WHY ? would not your plugins includes more than just migrations? Would not you be able to acheive everything you want with rails engines? if something is lacking in the rails engine, would not you want to extend this engine system instead?

I like the engine migrations approach and my “Migration Extensions” are based on that. But I don’t need Engines, the functionality I’m adding isn’t that of an Engine, and while I would want to use their migration system, I can’t because it’s tailored specifically for Engines.

There are 3 major problems with migrations that I tend to solve:

  1. All of them are in one place, numbered sequentially. Often a developer wants to start his own branch of migrations to avoid name or number collisions and to be able to pack them in a subdirectory or plugin. Rails Engines are a fine example of that need.
  2. Migrations change database schema, but often the user for the database (specified in database.yml) doesn’t have necessary priviledges. User should have two options: to make another entry in database.yml with the details for the db user used for migration runs; or to specify the user and password from command line.
  3. Migrations often rely on your application models. The trouble is after a while you have your schema ‘versioned’ through atomic schema updates (migrations), but older versions of your models are not kept. This opens up potential for oversight while writing new migrations that use models to update data - Typo blog had big problems with this. Some people put namespaced copies of their models at the time of writing the migration in the migration file. This is a good solution because that way you version your model parallel with your schema updates. This could be automatized so you don’t have to manually put your models in the migrations file.