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.