Namespaced Models in Rails: What's the State of the Union

Namespaced models in Rails: What's the state of the union?

The best writeup I've seen on the issue is from Strictly Untyped. To my knowledge 2.3 hasn't resolved any issues, which means they're still unreliable.

Should models be namespaced in Rails?

I've recently found this post but back from 2007 by Pratik Naik. Says there namespace in models doesn't really resemble databases. Uses something like below. Even there's a quote from DHH too.

Rails::Initializer.run do |config|
# Your existing stuff
config.load_paths << "#{RAILS_ROOT}/app/models/pets"
end

http://m.onkey.org/2007/12/9/namespaced-models

p/s: I don't know whether the post is still relevant or not, just something I found recently when I wanted namespaces in my models.

ActiveRecord WHERE with namespaced models

Is that normal that AR can infere table name of namespaced models from
relations in includes but can't in where?

Yes. This is an example of a leaky abstraction.

Assocations are an objection oriented abstraction around SQL joins, to let you do the fun stuff while AR worries about writing the SQL to join them and maintaining the in memory couplings between the records. .joins, .left_joins .includes and .eager_load are "aware" of your assocations and go through that abstraction. Because you have this object oriented abstraction .includes is smart enough to figure out how the module nesting should effect the class names and table names when writing joins.

.where and all the other parts of the ActiveRecord query interface are not as smart. This is just an API that generates SQL queries programmatically.
When you do .where(foo: 'bar') its smart enough to translate that into WHERE table_name.foo = 'bar' because the class is aware of its own table name.

When you do .where(demands: {user_id: 1}) the method is not actually aware of your associations, other model classes or the schema and just generates WHERE demands.user_id = 1 because that's how it translates a nested hash into SQL.

And note that this really has nothing to do with namespaces. When you do:

.where(reverse_auction_demands: {user_id: 1})

It works because you're using the right table name. If you where using a non-conventional table name that didn't line up with the model you would have the exact same issue.

If you want to create a where clause based on the class without hardcoding the table name pass a scope to where:

.where(
ReverseAuction::Demand.where(user_id: 1)
)

or use Arel:

.where(
ReverseAuction::Demand.arel_table[:user_id].eq(1)
)

How can I tell Rails to autoload namespaced models from a top-level directory?

No, you can't tell Rails to look for a qualified constant (like MyApp::User) at the top level of a directory in the autoload path like (app/models). When Rails sees MyApp::User (in code which is not inside a module definition) it will only look for my_app/user.rb in directories in the autoload path.

You could trick Rails a lot of the time by never using qualified constants. If your controllers are in the same namespace, the following would work:

app/controllers/my_app/users_controller.rb

module MyApp
class UsersController < ApplicationController
def index
@users = User.all
end
end
end

app/models/user.rb

module MyApp
class User < ActiveRecord::Base
end
end

Rails doesn't know whether the User referenced in the controller is top-level or in MyApp, so it would look for both app/models/user.rb and app/models/my_app/user.rb. Similarly, you could autoload namespaced models in app/models from other namespaced models.

However, you'd hit a wall (that is, you'd have to manually require the model class file) as soon as you needed to refer to a namespaced model from code which was not itself in a namespace, e.g. in the console or in a unit test. And it would be silly to have controllers in a subdirectory but models not in a subdirectory. And you'd be confusing anyone who looked at your code. So the best thing to do would be to follow Rails convention and put your namespaced models in a subdirectory of app/models.

Rails url_for namespaced models and non-namespaced controller

As mentioned in one of your comments above, you have

resources :widgets

in your config/routes.rb. In the location you're trying to set above with the url_for, you're trying to match the following route

widget GET    /widgets/:id(.:format)    widgets#show

But as you said, url_for w is instead causing rails to guess you're looking for the (non-existent) some_stuff_widget_path route. Instead change that line to

location = url_for widget_path(w)

Generating proper paths in namespaced Rails scaffolds

With rails build-in generators you can't.

See the generator source code to understand why:

<td><%%= link_to 'Show', <%= singular_table_name %> %></td>
<td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td>
<td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>

As you can see, it goes with edit_<%= singular_table_name %>_path to generate the edit path, without considering name-spacing. (And haml-rails does the same)


The best thing to do, if you have time and patience for it, would be to fix this on the codebase and propose a PR. That's the main point of open-source after all.

If you go this direction, have a look first at open issues, I haven't dive deep into but it seems that different conversations are going on about that matter. Like https://github.com/rails/rails/pull/13927 or https://github.com/rails/rails/issues/21652


Or you can use existing gems like Beautiful-Scaffold that seem to be supporting namepacing

Many rails Models with the same flag. What's the best practice?

If all you're looking for is a way to store all the functions in a single place, but have them accessible from all your flagable models, I'd recommend writing a mixin for them. For example, in lib/approved.rb, you could have the module:

module Approved

# Any approval functions/constants that don't belong in a model go here...

module Mixin
def self.included(klass)
klass.class_eval do
# Class-levell model macros can be run here
named_scope :approved, {:conditions => {:approved => true}}
named_scope :unapproved, {:conditions => {:approved => false}}
end
end

def approved?
return (self.approved == true)
end

# Other shared model functions go here...
end
end

And then it's just a matter of including the mixin in all the models that need those functions:

class Approvable < ActiveRecord::Base
include Approved::Mixin

# etc.
end

Hope that helps!

Stop Rails Console from loading Test::Unit

I just used a simple work-around of calling my class "Exam" instead of Test, but I would like to know how to resolve this correctly without working around the issue.

Rails good practice in generating controllers , models and views

I think scaffolding is rather bad because it generates a lot of stuff you probably don't want, I only use it for spike solutions.

In real projects my company rule book says that I have to use Test Driven Development (which I do like). Meaning that in a default approach (which sometimes I can't manage to follow) I start with an integration test and follow from it. (I build a route then a controller method then a view then a model...).



Related Topics



Leave a reply



Submit