views:

501

answers:

4

Hi there,

Been trying to sort this for over a day now, and I am sure that it is something simple that I am missing.

I have a project, which can have one main category and two optional categories. My relevant code for the project model:

  has_many :project_categories

has_one :optional_category_1, :through => :project_categories, :conditions => 'is_main_category = 0', :order => 'category_id', :source => :category, :class_name => 'Category'

has_one :optional_category_2, :through => :project_categories, :conditions => 'is_main_category = 0', :order => 'category_id DESC', :source => :category, :class_name => 'Category'

has_one :main_category, :through => :project_categories, :conditions => 'is_main_category = 1', :source => :category, :class_name => 'Category'

The relevant code from the Category class:

  has_many :project_categories

has_many :projects, :through => :project_categories, :source => :project

and from the ProjectCategory class:

    class ProjectCategory < ActiveRecord::Base
  belongs_to :project
  belongs_to :category
end

In my view:

    Main Category: <%= f.select(:main_category, Category.find(:all, :order => 'parent_id, categories.desc').collect {|c| [c.display_name, c.id] }, :prompt => "Select a Main Category") %><br>
Optional Category 1: <%= f.select(:optional_category_1, Category.find(:all, :order => 'parent_id, categories.desc').collect {|c| [c.display_name, c.id] }, :prompt => "Select an Optional Category") %><br>
Optional Category 2: <%= f.select(:optional_category_2, Category.find(:all, :order => 'parent_id, categories.desc').collect {|c| [c.display_name, c.id] }, :prompt => "Select an Optional Category") %><br>

and in my controller:

      @project.attributes = params[:project]

Ok, so when updating an existing project, I get the following error:

undefined method `update_attributes' for #<Class:0x82efce0>

and the relevant stack trace:

C:/Software/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/associations.rb:1255:in `main_category='

C:/Software/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:2745:in send' C:/Software/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:2745:in attributes=' C:/Software/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:2741:in each' C:/Software/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:2741:in attributes=' C:/Development/craftbits_rails/app/controllers/projects_controller.rb:85:in `manage_project'

Is it saying that there is an issue with main_category and that it is a generic class? But why? The association defines it correctly AFAIK.

Any help appreciated!

Vikram

A: 

Maybe change the controller to something like:

@project = Project.find(params[:id])
@project.update_attributes(params[:project])
Joel Meador
No, same error. Besides, I need to do other things to the project model before saving.
Vikram Goyal
A: 

You may need to use main_category_id in your view, i.e.

Main Category: <%= f.select(:main_category_id, ...) %>

You're calling update_attributes, but main_category isn't properly an attribute - it's an association. main_category_id is an attribute.

Sarah Mei
True. However, I am actually calling attributes (and not update_attributes) and when I replace main_category_id in the select tag, I get the error that project doesn't have the main_category_id attribute. It doesn't complain on main_category.
Vikram Goyal
A: 

Where are you getting @project from? Are you just doing a normal Project.find(params[:project_id]) or something?

Tried throwing in a debugger statement and seeing what class the @project is and what methods it has on it?

neiled
Thanks. Yes. The project is coming from this call: @project = (Project.find(params[:id]) unless !params[:id]) || Project.new
Vikram Goyal
And I think it is not the project class it is complaining on. If I remove the categories from within the view, it doesn't complain.
Vikram Goyal
A: 

I know this doesn't address the error you're getting, but I'd suggest using three one-to-many relationships instead of one many-to-many relationship.

The conventional purpose of has_many :through => ... (many-to-many) is for when you have something like students and classes. A student can be in any number of classes. A class can have any number of students. Totally arbitrary numbers on both sides of the relationship.

But that isn't your situation here. Your projects can be in exactly one main category, one optional category 1, and one optional category 2. It's a totally different problem and it isn't the problem that has_many :through is designed to solve.

I suggest this arrangement:

class Project < ActiveRecord::Base

  belongs_to :main_category, :class_name => "Category",
    :foreign_key => 'main_category_id'

  belongs_to :optional_category_1, :class_name => "Category",
    :foreign_key => 'optional_category_1_id'

  belongs_to :optional_category_2, :class_name => "Category",
    :foreign_key => 'optional_category_2_id'

end

class Category < ActiveRecord::Base

  has_many :main_category_projects, :class_name => "Project",
    :foreign_key => 'main_category_id'

  has_many :optional_category_1_projects, :class_name => "Project",
    :foreign_key => 'optional_category_1_id'

  has_many :optional_category_2_projects, :class_name => "Project",
    :foreign_key => 'optional_category_2_id'

end

Then you'll be able to do stuff like:

my_project.main_category

my_category.optional_category_1_projects

# etc...
Ethan
Excellent. I have never quite understood has_many through well enough. Part of the problem with this issue is that it is from a legacy database, where changing the structure may not be possible, without reworking a lot of other things. But this is a good suggestion, and I will try and see if I can do it. I wish I could figure out why my solution is not working, as it seems to be valid within the parameters of has_many through.
Vikram Goyal