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