Question

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

Was it helpful?

Solution

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...

OTHER TIPS

Maybe change the controller to something like:

@project = Project.find(params[:id])
@project.update_attributes(params[:project])

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.

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?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top