Keeping track of how things are supposed to work at work is hard. Nobody likes getting asked the same question over and over, especially ones that probably have answers buried in Slack or old emails. Socratic attempts to solve this problem. This app allows users to post questions and allows multiple users to answer one question. If a particular answer turns out to be most useful, the person who asked the question can mark this answer with a green check mark.
One of the biggest hurdles I faced during this project was implementing the following requirement: You must include a nested form that writes to an associated model through a custom attribute writer.
A nested form handles more than one model object at a time. With nested forms, users can submit one form, as opposed to a creating new form for each model object. In my case, I used a nested form that allows users to submit a question and tags on the same form.
Setting Up Object Relationships
Because questions can have multiple tags and tags can belong to multiple questions, I created the join table, QuestionTags. You can see how I mapped this has_many :through
, along with all the other relationships here.
Creating a Nested Form
Next, I added the tag_name
attribute in question form:
<%= form_for @question do |f| %>
<%= f.label :summary %>
<%= f.text_field :summary %>
<%= f.label :tag_names, "Tags" %>
<%= f.text_field :tag_names %>
<%= f.label :content, "Question" %>
<%= f.text_area :content %>
<%= f.submit %>
<% end %>
Updating Strong Params
I also added the new custom associated attribute to the permitted params in the QuestionsController
. Now, our newly created attribute is on the model’s whitelist of accepted attributes.
class QuestionsController < ApplicationController
...
private
def permitted_question_params
params.require(:question).permit(:content, :summary, :tag_names)
end
end
Attribute Customization
I also needed to create the associations in the parent model. In this case, I created readers and writers for tag_names
in the Question model. Users can enter tags (i.e., “coffee, break”) and the custom writer will check the database to ensure that duplicate tags are not created through the find_or_initialize_by
method.
class Question < ApplicationRecord
has_many :question_tags
has_many :tags, through: :question_tags
validates_presence_of :content, message: "Empty questions are not virtuous"
validates_presence_of :summary, message: "Brevity is the soul of wit, so summary is required"
def tag_names
self.tags.map(&:name).join(", ")
end
def tag_names=(names)
self.tags = names.split(",").map(&:strip).map do |name|
Tag.find_or_initialize_by(name: name)
end
end
end
My final nested form looks pretty simple, but there was a lot of chin scratching along the way.