rails nested form with has_many :through relationship - ruby-on-rails

Here are my models:
class Project < ActiveRecord::Base
has_many :project_applications
has_many :questions
accepts_nested_attributes_for :questions, :allow_destroy => true, :reject_if => proc { |a| a[:content].blank? }
end
class Question < ActiveRecord::Base
belongs_to :project
has_many :answers
has_many :project_applications, through: :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :project_application
end
class ProjectApplication < ActiveRecord::Base
belongs_to :project
belongs_to :student
has_many :answers
has_many :questions, through: :answers
end
A project is created by an employer, and a student can create a project_application. The project_application should present the questions and then show form fields that correspond to the questions answers. I cannot for the life of me figure out how the form view should look. I need a form_for ProjectApplication that accepts nested attributes for answers. I have the following in my controller:
class ProjectApplicationsController < ApplicationController
def new
#project = Project.find(params[:project_id])
#project_application = ProjectApplication.new
#project_application.project = #project
#project_application.project.questions.each do |question|
#answer = question.answers.build
#answer.project_application = #project_application #this line does not work
puts 'answer' + #answer.inspect.to_s
end
puts 'here are the answers' + #project_application.answers.inspect.to_s
end
end
The problem with this is that the answers are not correctly being associated with project_applications because the project_applications don't have an id yet (because they have not been created) so the association can't happen, so the answer fields are not displayed. Here is the view code (does not work) that I have now:
<%= form_for #project_application, url: project_project_applications_path(#project.id), method: :post, remote: true do |f| %>
<%= f.fields_for :project do |proj| %>
<%= proj.fields_for :questions do |quest| %>
<%= quest.fields_for :answers do |answer| %>
<%= answer.text_area :content %>
<% end %>
<% end %>
<% end %>
<%= f.submit "APPLY" %>
<% end %>
How do I change the view and/or controller to properly display answer fields correctly associated with questions and the project application?

My understanding:
You have projects
Each project has many questions & project_applications
Each question belongs to a project, has many answers through project_applications
For each project, you'd like to create applications with the applicable answers. Here's what I'd do:
Models
class Project < ActiveRecord::Base
has_many :project_applications
has_many :questions
end
class Question < ActiveRecord::Base
belongs_to :project
has_many :answers
has_many :project_applications, through: :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :project_application
end
class ProjectApplication < ActiveRecord::Base
belongs_to :project
belongs_to :student
has_many :answers
has_many :questions, through: :project
accepts_nested_attributes_for :answers
def self.build
application = self.new
application.answers.build
end
end
Controller
#app/controllers/project_applications_controller.rb
def new
#projectapplication = ProjectApplication.build
#project = #projectapplication.project
end
def create
end
private
def application_params
params.require(:project_application).permit(:application, :attributes, :here, answer_attributes: [:content])
end
Views
#app/views/project_applications/new.html.erb
<%= form_for #projectapplication do |f| %>
<%= f.text_field :application_fields %>
<% #project.questions.each do |question| %>
<%= f.fields_for :answers, question do |answer| %>
<%= answer.hidden_field :question_id, question.id %>
<%= answer.text_field :content, placeholder: question.content %>
<% end %>
<% end %>
<% end %>
Process
This works by creating a new project_application, sending answers directly to the answer model. Because answers are directly associated with questions, and projectsthrough questions, you should be able to get it working without passing any more data
This might not work outright, but I'm sure with some tweaking it will deliver the desired result

Related

Rails 4 - Validate that at least one checkboxe is checked in nested form inside a loop

I try to create a validation that work in a nested for with a collection of checkboxes.
The collection is inside a each.do loop.
<%= simple_form_for #questionnaire do |f| %>
<% #questions.each do |question| %>
<%= question.name %>
<%= f.collection_check_boxes :answer_ids, Answer.where(question_id: question.id), :id, :content %>
<% end %>
<% end %>
This is inside a Questionnaire form.
So I try to validate answer in the Questionnaire model, but I need to do it for each question : each question should at least have a checkbox checked.
Any idea would be great!
Thank you in advance!
EDIT :
The relationship between my 3 models :
class Questionnaire < ActiveRecord::Base
has_many :answers
has_many :answers, :through => :answers
end
class Question < ActiveRecord::Base
has_many :questionnaires
has_many :answers, through: :questionnaires
end
class Answer < ActiveRecord::Base
belongs_to :question
has_many :questionnaires, :through => :answers
end
You need to validate answer_ids like below
validates :answer_ids, presence: true
This line put on your model
I think to help you

Nested forms with has_one :through

I'm having some trouble to implement a nested form with a has_one :through association.
Models
# model: member.rb
belongs_to :user
has_one :academic
# model: user.rb
has_one :member
has_one :academic, through: :member
accepts_nested_attributes_for :member, reject_if: :all_blank
accepts_nested_attributes_for :academic, reject_if: :all_blank
# model: academic.rb
belongs_to :member
belongs_to :user
Controller
# users_controller.rb
def new
#user = User.new
#user.build_member
#user.build_academic
end
I also have tried with:
#user.member.build_academic
View
# new.html.erb
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |ff| %>
<%= ff.text_field :email %>
# member belongs to user so I can call a fields_for
<% ff.fields_for :member do |f| %>
<%= f.text_field :name %>
# this part is not shown. What is wrong with my association?
<% f.fields_for :academic do |a| %>
<%= a.text_field :major %>
<% end %>
<% end %>
<% end %>
I've taken a look into the Rails documentation. The first fields_for is shown in the page (:member), but the second one (:academic), which has the has_one :through association, is not shown in the page.
Any help will be appreciated. Thank you.
Through
If you want to build your data through a relation, you have to pass the associated data as it's constructed:
#app/models/member.rb
class Member < ActiveRecord::Base
belongs_to :user
has_one :academic
accepts_nested_attributes_for :academic
end
#app/models/user.rb
class User < ActiveRecord::Base
has_one :member
has_one :academic, through: :member
accepts_nested_attributes_for :member
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
belongs_to :member
belongs_to :user
end
This will allow you to do the following:
#app/controllers/members_controller.rb
class MembersController < ApplicationController
def new
#member = Member.new
#member.build_member.build_academic
end
def create
#member = Member.new member_params
#member.save
end
private
def member_params
params.require(:member).permit(:x, :y, :z, academic_attributes: [:some, :attributes, member_attributes:[...]])
end
end
This would permit the following:
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
<%= f.fields_for :member do |m| %>
<%= f.text_field :name %>
...
<%= m.fields_for :academic do |a| %>
<% a.text_field :name %>
...
<% end %>
<% end %>
<%= f.submit %>
<% end %>
This works to build a new member object, and a new academic object from the user. Although not strictly what you're asking, it looks like it could benefit you in some form.
Associations
If you want to do what you're asking (IE build_member and build_academic exclusively), you'll need to get rid of the has_one :through relationship...
#app/models/user.rb
class User < ActiveRecord::Base
has_many :memberships
has_many :academics, through: :memberships
end
#app/models/membership.rb
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :academic
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
The problem is you're basically trying to build a relationship for a direct association (member) and an indirect relationship (academic).
If you want to build both exclusively, you have to make them have a direct association with your main model. The above should allow you to do the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.members.build.academics.build
end
end
Much like my top example, this will pass nested data through your form - if you wanted to have it completely exclusively, do this:
#app/models/user.rb
class User < ActiveRecord::Base
has_one :member
has_one :academic
has_and_belongs_to_many :academics
end
#app/models/member.rb
class Member < ActiveRecord::Base
belongs_to :user
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :users
end
This will allow the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.build_member
#user.build_academic
end
def create
#user = User.new user_params
#user.save
end
private
def user_params
params.require(:user).permit(:user, :params, member_attributes: [], academic_attributes:[])
end
end
For future reference or for anyone that has the same problem, I found a way around to fix this problem, in case you have same structure.
I could fix this by going to the view, before the form, and writing:
<% resource.member.build_academic %>
In my case resource is User, set by devise, a Rails gem used for authentication.
And you don't need to reference any :through or whatsoever in your model.
It is not the most efficient way, but I haven't found any other solution. Hope it helps.

allowing user to create answers to multiple questions in one form

I have a model relationship set up like this:
# User model
class User < ActiveRecord::Base
belongs_to :company
has_many :answers
# groups
has_many :group_memberships
has_many :group_questions, through: :groups, source: :questions
has_many :groups, through: :group_memberships
# question
has_many :question_participants, as: :questionable
has_many :questions, through: :question_participants
# questions created by admin
class Question < ActiveRecord::Base
belongs_to :company
has_many :question_participants
has_many :answers
has_many :users, through: :question_participants,
source: :questionable, source_type: 'User'
has_many :groups, through: :question_participants,
source: :questionable, source_type: 'Group'
has_many :companies, through: :question_participants,
source: :questionable, source_type: 'Company'
end
# user answers to questions
class Answer < ActiveRecord::Base
belongs_to :user
belongs_to :question
end
now my answer stores user_id, reply (their answer) and question id.
What I'd like to be able to create now is a form that allows a user to answer all the questions asked to them, and submit the answers.
I've setup my form like this on the view file (answers#new):
<%= form_for #answer do |f| %>
<% current_user.all_questions.each do |question| %>
<%= f.hidden_field :question_id, value: question.id %>
<p><%= question.name %>
<%= f.text_field :reply %>
<% end %>
<% end %>
which doesn't work, and I get why - issue is i don't know how to make it work with multiple save.
I hope I got your problem. Here is what I suggest:
<%= form_for #answer do |f| %>
<% current_user.all_questions.each do |question| %>
<%= hidden_field_tag 'questions[][id]', question.id %>
<p><%= question.name %>
<%= text_field_tag 'questions[][reply]' %>
<% end %>
<% end %>
Now in the controller action you can access params[:questions] and you'd get smth like this: [{"id"=>"1", "reply"=>"some text 1"}, {"id"=>"2", "reply"=>"some text 2"}]:
# your_controller.rb
def create_answers
answers = params[:questions].map do |question|
current_user.answers.create(question_id: question[:id], reply: question[:reply])
end
if answers.any(&:invalid?)
flash[:error] = 'Some answers were not accepted'
redirect_to :back
else
redirect_to home_path
end
end

Using a Many-to-Many Relationship with the Public Activity gem in Rails

I have the scenario where an author has and belongs to many books, vice versa. Following the instructions for setting up associations in a one-to-many relationship works fine but when a many-to-many relationship introduced I get this error message whenever I try to create or update my book model.
undefined method `author' for #<Book:0x007fb91ae56a70>
As far as setting up how authors are chosen for a book I'm using the code provided by the token-input railscast here with a few alterations.
class Author < ActiveRecord::Base
has_many :authorships
has_many :books, through: :authorships
def self.tokens(query)
authors = where("name like ?", "%#{query}%")
if authors.empty?
[{id: "<<<#{query}>>>", name: "Add New Author: \"#{query}\""}]
else
authors
end
end
def self.ids_from_tokens(tokens)
tokens.gsub!(/<<<(.+?)>>>/) {create!(name: $1).id}
tokens.split(',')
end
end
class Book < ActiveRecord::Base
attr_reader :author_tokens
include PublicActivity::Model
tracked owner: :author
has_many :authorships
has_many :authors, through: :authorships
def author_tokens=(ids)
self.author_ids = Author.ids_from_tokens(ids)
end
end
Form View
<%= form_for(#book) do |f| %>
...
<div class="field">
<%= f.text_field :author_tokens, label: 'Author', input_html: {"data-pre" => #book.authors.to_json} %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
There is no author relationship in your Book model.
What
tracked owner: :author
does is basically calling method author on your Book instance. You should try :authors
But!
That won't solve your problem because owner can only be one. So you can do something like:
tracked owner: proc {|_, book| book.authors.first }
to set the owner to the first author the book has.
class Author < ActiveRecord::Base
has_many :author_books, inverse_of: :author, dependent: :destroy
accepts_nested_attributes_for :author_books
has_many :books, through: :author_books
end
class Book < ActiveRecord::Base
has_many :author_books, inverse_of: :book, dependent: :destroy
accepts_nested_attributes_for :author_books
has_many :authors, through: :author_books
end
class AuthorBook < ActiveRecord::Base
validates_presence_of :book, :author
end
============= view ==============
<%= form_for #book do |f| %>
<%= f.text_field :title %>
<%= f.fields_for :author_books do |f2| %>
<%# will look through all author_books in the form builder.. %>
<%= f2.fields_for :author do |f3| %>
<%= f3.text_field :name %>
<% end %>
<% end %>
<% end %>

Rails conditional_select for nested attributes

I'm trying to setup the following: A User has many Groups through Memberships, a Group has many Events, and an Event has many Posts.
On my view to show a group with all of its events, I want a user to be able to write a new post by selecting the correct group from a drop down, writing a comment and submit. I'm currently using a collection_select to create the post, but the event_id is not getting passed to ActiveRecord, i.e. posts are created, but they do not have event_ids (or even comments):
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through: :memberships
has_many :posts
end
class Membership < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :events, dependent: :destroy
has_many :users, through: :memberships
end
class Event < ActiveRecord::Base
belongs_to :group
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :event
belongs_to :user
end
class GroupsController < ApplicationController
def show
#define new post
#new_post = Post.new
end
end
class PostsController < ApplicationController
def create
if #post = Post.create(params[post_params])
flash[:success] = "Post Created!"
else
redirect_to group_url
end
end
private
def post_params
params.require(:post).permit(:event_id, :comment)
end
end
<h1>New Post:</h1>
<%=form_for([#new_post]) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class = "field">
<%= f.label :event_name %>
<%= f.collection_select(:event_id, Event.all, :id, :title) %>
</div>
<div class = "field">
<%= f.text_area :comment, placeholder: "New Post..." %>
</div>
<%=f.submit "Submit", class: "btn btn-large btn-primary" %>
<%end%>
I have a feeling that because the routes are nested, group_id never is passed to the Posts controller, and so can never be set. But I'm sure there's a lot more wrong than that...
can you try to pass Post.create(post_params) instead of Post.create(params[post_params])
post_params is actually a full hash extracted from the params so you should not pass it to params again
If you want to add user_id
you should add to your view something like this
<%= f.hidden_field :user_id, value: current_user.id %>

Resources