Namespacing Models

Hello everyone,

Do you feel it? The little shiver down your spine when you think of "namespacing" in rails? To some thats a common feeling, to people like myself who enforce absolute organization have already thought about the need for namespaces in rails.

Now I do understand this is an ongoing effort... to some degree. Controllers and routes, from my experiences work well, that being, under a Namespace::Classname convention.

At this stage, we need to solidify a convention for namespaces with models. It seems the functionality is halfway there and we need some sort of closure on the issue. As it stands, the easy solution which many people has been using is to just prefix the class name with the "namespace". For example,

class SecurityUser < ActiveRecord::Base end

This also means that associations will also reference the naming convention :security_users. The name used to prefix the class is used throughout the application... redundant! However, what happens if the User class is moved to another "package" or if there is a spelling mistake? The entire application will require refactoring when it should only be changed in a few places pertaining to the namespace. This lends itself for a better solution.

I will agree that for most applications, they don't reach the girth where they even need to take advantage of this type of functionality, but Rails is compared against "enterprise" web application platforms where namespaces are a standard. Name conflicts will happen when reusing modules throughout various applications.

It's simple.

app/models/security/ app/models/security/user.rb

class Security::User < ActiveRecord::Base end

by default, the table name should be security_user and the associations can remain :users. There shouldn't need to be a app/ models/security.rb class that references the table "securities".

I am not a rails core developer, so I do not have the roadmap of the architecture in mind. But there should be something taken into consideration. I'd be willing to make the contribution, I'd just like to hear some more thoughts from other developers, thanks.

Regards,

Peter

At this stage, we need to solidify a convention for namespaces with models. It seems the functionality is halfway there and we need some sort of closure on the issue.

I think it's simply a matter of fixing the parts which don't work as expected, and then providing a small section of documention in ActiveRecord::Base about models within modules.

Do you know which parts are currently broken, or don't work as expected? AFAIK associations, STI and autoloading work just fine with namespaced models.

The only thing I can see as possibly confusing is the the current behaviour for table_name:

>> module Security; end => nil >> class Security::User < ActiveRecord::Base; end => nil >> Security::User.table_name => "users"

It might be more sensible to have a table_name of "security_users", though it irks me that it's the same result as the table_name for a SecurityUser class.

-- tim

The only problem I have with namespaced models is that STI class names are demodulized when they are stored. It would be great to see this fixed in 2.0. There have been a couple of tickets about it ( http://dev.rubyonrails.org/ticket/6792) and I’ve got a plugin that fixes the problem, http://svn.spreydon.org.nz/things/rails/plugins/namespaced_models/ .

-Jonathan.

Associations do generally work but you have to be explicit about the class name to avoid some issues, e.g:

class Shop::Basket < ActiveRecord::Base    has_many :items, :foreign_key => 'basket_id',                     :class_name => '::Shop::basket::Item' end

class Shop::basket::Item < ActiveRecord::Base    belongs_to :basket, :foreign_key => 'basket_id',                        :class_name => '::Shop::Basket'    belongs_to :product, :foreign_key => 'product_id',                         :class_name => '::Shop::Product' end

To fix this compute_type method could be a bit more intelligent and it'd be possible to drop the explicit class name. There's also a small bug with association extension modules not being able to be defined by passing a block.

The main bug with STI is that the demodulized class name is stored and not the full class name.

Like you said, it mostly works it just needs someone to spend a couple of days going through and addressing all these small issues.

Andrew White

The only thing I can see as possibly confusing is the the current behaviour for table_name:

>> module Security; end => nil >> class Security::User < ActiveRecord::Base; end => nil >> Security::User.table_name => "users"

It might be more sensible to have a table_name of "security_users", though it irks me that it's the same result as the table_name for a SecurityUser class.

class Security < ActiveRecord::Base   class User < ActiveRecord::Base   end end

Security::User.table_name

Yep... like how your ol dynamically-generating-an-inner-class-with-associations-to-the-outer-class-at-runtime-mind-f*ck-known-as-acts_as_versioned plugin works :slight_smile: :slight_smile:

I only mentioned it because the Module::Class table_name convention might be the only part that's slightly illogical (rather than have it similar to camping, module prefixed)... but hey, I don't mind keeping it as-is and having people set table_name to "security_users" manually

-- tim

Andrew White wrote:

Do you know which parts are currently broken, or don't work as expected? AFAIK associations, STI and autoloading work just fine with namespaced models.

Associations do generally work but you have to be explicit about the class name to avoid some issues, e.g:

class Shop::Basket < ActiveRecord::Base    has_many :items, :foreign_key => 'basket_id',                     :class_name => '::Shop::basket::Item' end

class Shop::basket::Item < ActiveRecord::Base    belongs_to :basket, :foreign_key => 'basket_id',                        :class_name => '::Shop::Basket'    belongs_to :product, :foreign_key => 'product_id',                         :class_name => '::Shop::Product' end

To fix this compute_type method could be a bit more intelligent and it'd be possible to drop the explicit class name. There's also a small bug with association extension modules not being able to be defined by passing a block.

The main bug with STI is that the demodulized class name is stored and not the full class name.

Like you said, it mostly works it just needs someone to spend a couple of days going through and addressing all these small issues.

Andrew White

I'm running into some weirdness with namespaced models.

It works perfectly on my dev machine (MacOS X, ruby 1.8.6., rails 1.2.3).

However, on my production machine (Ubuntu, ruby 1.8.4, rails 1.2.3), when ever I try to access a namespaced class, i.e. X::Y, I get the following errors:

NameError: uninitialized constant X         from /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:266:in `load_missing_constant'         from /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:452:in `const_missing'         from /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:464:in `const_missing'         from (irb):1

Is this a known issue with ruby 1.8.4?

Thanks,

Andrew

Andrew,

I've seen problems like this, especially but not limited to when moving an app from OS X to linux. It's the class auto-loading that's the culprit. Rails' auto-load mechanism works great when you follow convention, but if you stray (e.g., heavy use of modules) then unexpected things may happen. Regarding the difference between OS X and linux, I suspect this more often than not has something to do with linux being case-sensitive and OS X being only somewhat case-sensitive. Our solution has been to explicitly load all of our classes in the order we want them loaded.

At the bottom of application.rb, we load all the models. For each module (in the order we want them loaded) we do the following:

Dir["#{RAILS_ROOT}/app/models/{module}/*.rb"].each { |file|   require_dependency "{module}/#{file[file.rindex('/') + 1...-3]}" }

Replace {module} with your module name. We do it in application.rb, and with require_dependency, so that classes continue to reload correctly on each request.

It's not the _best_ solution in the world, since now _all_ classes load on _every_ request in development mode, but it works and hasn't caused us any problems.

Incidentally, this is also the solution to get Object.subclasses_of to work properly in all cases, as well as finders in STI tables. Before all classes are loaded these methods can behave unpredictably, as they are unaware of the entire class hierarchy.

Dan Chak