Has many through with an extra field

Hello,

I’m trying to solve a common task with many to many relationships.

Here are my class definitions:

class User < ActiveRecord::Base
has_many :questionnaires
has_many :forms, :through => :questionnaires
end

class Form < ActiveRecord::Base
has_many :questionnaires
has_many :users, :through => :questionnaires
end

class Questionnaire < ActiveRecord::Base
belongs_to :user
belongs_to :form
end

My problem is to include an extra date field named active_at to User
new/edit form.

When creating a user I need to check forms (via checkboxes) which it
belongs to. I know it can be done by creating checkboxes with name like
‘user[form_ids][]’. But I also need to enter a date field for each of
checked relations which will be stored in the questionnaire join model.

Any help is very appreciated
Bodo

On Wed, Feb 15, 2012 at 10:13 AM, Bodo K. [email protected]
wrote:

My problem is to include an extra date field named active_at to User
new/edit form.

When creating a user I need to check forms (via checkboxes) which it
belongs to. I know it can be done by creating checkboxes with name like
‘user[form_ids][]’. But I also need to enter a date field for each of
checked relations which will be stored in the questionnaire join model.

Any help is very appreciated

We recently had a long (and confusing) discussion about this

https://groups.google.com/forum/#!topic/rubyonrails-talk/W-FTZNPNUeE/discussion

Looking back at it, I would suggest take it step-by-step.

Try first to understand how to get it to work on the model level, write
unit tests for
that and only after that, build your new/edit view code that will
generate
the params
hash that will fill in the values.

There is a good chance you will need to do some “manual” tweaking to
completely build up the datastructure from the params hash. I mean a
simple user = create(params[:user]) may require you defining some
additional setter methods.

Maybe, the core is that you will need to override the

form_ids=(id,id,…)

method on the User model (start with

rails c

User.new.methods.grep(/form_ids=/)

to see if it is defined.

Assuming you use Rails 3.2.x you could start playing with
(UNTESTED code, probably not optimal, just a hint):

class Questionnaire < ActiveRecord::Base
belongs_to :user, :inverse_of => :users
belongs_to :form, :inverse_of => :forms # important for the save after
build !

google this line: "The last line ought to save the through record (a

Taggable). This will only work if the :inverse_of is set:"
end

class User < ActiveRecord::Base
has_many :questionnaires
has_many :forms, :through => :questionnaires

def form_ids=(form_id_array)
super # will pass the argument to higher-up function and build the
associated forms
self.forms.each do |form|
# I presume this will be populated by now
qs = form.questionnaires
qs = qs.select{|q| q.user == self} # filter only those that are
this
user
raise “BOOOM” if qs.size > 1 # there can be only 1 (check it to
be
sure)
q = qs.first
q.active_at Time.now
end
end
end

Then check the result manually and with tests.

Then save the user and check if all is still correct.

Once you can set the active_at to Time.now, a next phase can start to
set it to actual values, that are gotten from the form (probably need
to make a non-standard input format that may be an array of hashes
with in each entry the form_id and the date for that form_id ??).

I hope this helps, but I keep finding this non-trivial …

If I overlook the obvious, standard solution, I would be glad to be
corrected.

Peter

Thanks.

I forgot to mention that one solution is discussed on

When I try this approach I got an error message from inside the original
routine below:

Couldn’t find Questionnaire without an ID

(In my case Product is User, Category is Form and Categorization is
Questionnaire.)

def initialized_categorizations # this is the key method
[].tap do |o|
Category.all.each do |category|
if c = categorizations.find { |c| c.category_id == category.id }
o << c.tap { |c| c.enable ||= true }
else
o << Categorization.new(category: category)
end
end
end
end

My question is what the error line tries to do in detail.

Anyway, I’m surprised that for this common problem there is still no
reasonable solution available.

Regards
Bodo

On Wed, Feb 15, 2012 at 11:55 AM, Bodo K. [email protected]
wrote:

Couldn’t find Questionnaire without an ID
o << Categorization.new(category: category)
end
end
end
end

My question is what the error line tries to do in detail.

In that case, could you then send your particular implementation
and the stack trace that you got.

A wild guess is that there is a problem where looking for
an ID (that is only generated when saving to the database;
but at this time your questionaire object is still in the making
in memory and does not have an id yet).

That is why I used the “select” code in my proposal (checks
in the array in memory using the belongs_to result == self)
and not find (which would look for an entry in db, but we have
not saved yet at that point).

HTH,

Peter