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

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

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

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