Habtm and collection_select question


#1

I apologize in advance for my ignorance, but I’m stuck with a problem
and after a couple of hours of looking for answers in the documentation
and help, I’m still stuck so I was wondering if someone might have the
answer.

I have tables Projects and Stages, each with an habtm relationship to
each other, and a projects_stages table which contains projects_id,
stages_id and a date field. (A project can have any number of stages,
and completed stages are recorded in project_stages.)

On …/projects/edit I display the completed steps for the project (all
records in projects_stages which have project_id for the selected
project). So far so good.

Now I want a drop-down field to select the next step and write a new
record to the projects_stages field. So I put the following:

<%= collection_select(:project, :stages, Stage.find_all, :id,
:description, {:include_blank => true}) %>

The select box populates correctly, but when saving I get an error that
Stage expected but received String. The error page shows that the
stage_id is being passed back to the controller, but in this instance
what I need is to add a row to the project_stages table containing both
the project_id and the stages_id. Does Rails ‘magically’ do this (like
it does with a has_many relationship) and I’m just using
collection_select incorrectly, or is there some other way I’m supposed
to do this? I’m not sure what is the proper approach in this case.

Thanks.


#2

zero halo wrote:

<%= collection_select(:project, :stages, Stage.find_all, :id,
:description, {:include_blank => true}) %>

The select box populates correctly, but when saving I get an error that
Stage expected but received String. The error page shows that the

If I understand you correctly, the user is selecting the step that
they’ve just completed, to be added to the project.stages completed
list.

In this case I’d recommend you create an attr_accessor in your
project model called newly_completed_stage_id, then use

<%= collection_select(:project, :newly_completed_stage_id,
Stage.find_all - @project.stages, :id, :description, {:include_blank =>
true}) %>

and have a before_save method in Projects to add any new stage:

stages.push_with_attributes(
Stage.find(newly_completed_stage_id.to_i), :date_completed => Date.today
) unless newly_completed_stage_id.blank?


We develop, watch us RoR, in numbers too big to ignore.


#3

mrj wrote:

In this case I’d recommend you create an attr_accessor in your
project model called newly_completed_stage_id, then use

<%= collection_select(:project, :newly_completed_stage_id,
Stage.find_all - @project.stages, :id, :description, {:include_blank =>
true}) %>

and have a before_save method in Projects to add any new stage:

stages.push_with_attributes(
Stage.find(newly_completed_stage_id.to_i), :date_completed => Date.today
) unless newly_completed_stage_id.blank?

Awesome, thanks! I must say, I have a hard time wrapping my head around
some of the Rails behind-the-scenes magic, such as understanding exactly
how push_with_attributes knows to add a new record to my project_stages
join table, but hey, it works! I should have gotten it from reading the
Agile book, but my brain’s a bit foggy these days. This might be helpful
to post on the Rails wiki. I’ll attempt a write-up though I’m not sure I
can explain it all like it should.

I also liked the ‘Stage.find_all - @project.stages’ little bit of code.
I didn’t realize you could subtract one collection from another like
that. Very cool.


#4

zero halo wrote:

to post on the Rails wiki. I’ll attempt a write-up though I’m not sure I
can explain it all like it should.

The job of push_with_attributes is to add an object to the list of
objects in a HABTM association. To do this it must add a record to the
join table, geting the project_id from the Project object on which
stages is called, the stage_id from the first parameter Stage object,
and the attributes associated with the join from the second parameter.
The stage object itself is not saved because it is not a new record.

I also liked the ‘Stage.find_all - @project.stages’ little bit of code.
I didn’t realize you could subtract one collection from another like
that. Very cool.

That works because Rails cleverly uses an AR object’s id attribute for
equality comparison.

One other thing: find_all is depricated, use find(:all) instead.


We develop, watch us RoR, in numbers too big to ignore.