noob: where to put the find(:all) to use with collection_select() in multiple places?

Hey All,

I'm just starting out w/rails & have a toy app w/just 2 types of things right now: People and Organizations. Person :belongs_to organization and Organization :has_many people.

I'd like to provide a select control showing the available organizations on the people/new and people/edit actions (or whatever they're called) and so I'm using collection_select to do that. My question is where to put the call to Organization.find(:all) to grab out the data I need to populate the select control. It seems like I should be able to do that just once & stash the results in a class var for use anyplace in the people controller class.

But when I go to put that list in a class variable in my (scaffold- generated) people_controller.rb file like so:

  class PeopleController < ApplicationController

    @@organizations = Organization.find(:all)

    def new       # blah blah blah     end

    # etc...

  end

And then try to access this from views/people/new.html.erb w/this line:

    <%= collection_select(:person, :organization_id, @@organizations, :id, :name) %>

I get a NameError on that call to collection_select() w/msg "uninitialized class variable @@organizations in ActionView::Base::CompiledTemplates"

So... where can I stash the results of that .find(:all) call so that the same data is available for both actions?

Thanks!

-Roy

Hi,

    <%= collection_select(:person, :organization_id, @@organizations, :id, :name) %>

Even if this would work, I'd strongly advise against it since the view has more knowledge of your controller than it needs to and turns your nice MVC app into a tangled mess. Just use a normal instance variable here (@organizations). That gives the controller a chance to manipulate the organizations list (e.g. certain users may only create people for a subset of all available organizations, etc.) without requiring changes in the view.

Regarding the controller: Isn't it possible that the list of organizations changes? Your current code doesn't account for that. In this case the right way is a simple

    def new       @organizations = Organization.find(:all)       ...

    def edit       @organizations = Organization.find(:all)       ...

If the organizations don't change, and you _really_ want to load the only once, just add

    def new       @organizations = @@organizations       ...

HTH,   Stefan

Thanks for the response Stefan!

You're absolutely right that organizations can change--in the fullness of time I'm going to want to stash that list in whatever rails has for an application-wide cache, and refresh it whenever the list changes. (I think there are activerecord hook methods I can use for that, aren't there?).

But for now I'm just trying to get oriented to rails. It seems strange to me that instance vars are available in the view, but class vars aren't, and putting multiple calls to .find(:all) seems overly chatty w/the database, and not as DRY as could be.

Maybe what I need to do is just read up on caching in rails & see what I can see on that...

Thanks!

-Roy

Roy Pardee wrote:

But for now I'm just trying to get oriented to rails. It seems strange to me that instance vars are available in the view, but class vars aren't, and putting multiple calls to .find(:all) seems overly chatty w/the database, and not as DRY as could be.

Maybe what I need to do is just read up on caching in rails & see what I can see on that...

It's not that this isn't DRY, it's that each call to the method must allow for the fact that the Organization model changes. The controller shouldn't assume that it is the only way of accessing a model which is what you'll do by updating a controller class variable whenever the controller changes a model. This will lead to problems down the line if you end up with other accesses to the model. This may not seem important for this application, but it would be a bad habit to get into as it will definitely bite you on a larger project.

It is unlikely you are expecting a large number of organisations as that would make your select list unwieldy. So obtaining the list from the database should be fairly low cost. Let your database handle data caching and let your application handle access control and logic. When doing a find(:all) the database will do a bulk load in one call which shouldn't be much more expensive than getting a single row for small amounts of data (and so comparable to the cost of adding the new People record) and if you are using a decent database, it may have cached the results for you anyway. Also, at this stage, you don't know how noticable caching this value yourself will be. Use profiling to decide where to target performance efforts.

When starting out, it's probably more useful to concentrate on functionality rather than minor details and look at how you can refactor later once the application takes shape. As you play with more applications and look at other's code, you'll start recognising opportunities for factoring earlier in development.

Finally, if you really want to cache the records, put them where they belong. In the model. All access must go through the model, so use the model's after_* hooks to keep a cache in the model and provide access to this cache (which will even be available from a view if you really need it there).

Ah, that makes perfect sense--thanks much for the response. I definitely take the point that it's too early at this point to fuss over performance. Like I say, I'm still getting oriented--and not just to rails I guess, but to MVC in general.

I got together w/some friends last night & they helped me to write this bit here, which seems to work (even as the list of orgs changes) *and* satisfies my db-chattiness concern, I think, w/out breaking the MVC separation of concerns.

  class Organization < ActiveRecord::Base     has_many :people     def self.get_list       @@all_organizations ||= self.find(:all, :select => 'id, name')     end     def before_save       @@all_organizations = nil     end   end

That gives me an Organization.get_list method that I can call from my Person views (and any view? I guess probably...).

Thanks!

-Roy

Doesn't Rails 2.0 already do SQL caching?