I have a Poll app with 3 models.
Poll.rb
class poll < ApplicationRecord
validates_presence_of :user, :title
belongs_to :user
has_many :questions, dependent: :destroy
has_many :options, through: :questions
accepts_nested_attributes_for :questions
end
Question.rb
class Question < ApplicationRecord
validates_presence_of :poll_id, :question_id, :title
belongs_to :poll
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['title'].blank? }
end
Option.rb
class Option < ApplicationRecord
validates_presence_of :question_id, :title
belongs_to :question
belongs_to :poll
end
I want the question form to have a field for adding options so I've added this to the question _form.
<%= form.fields_for :option do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
I can now see an option block which is good. Although I wish to have 3 possbile options so in the questions_controller.rb I've added the following:
def new
#question = #poll.questions.build
3.times { #question.options.build } # 3 different options
end
Despite this I'm only seeing one option block instead of the 3. Why is this the case and how do i fix? Additionally I'm not seeing new entries into the options postgresql table.
Full questions_controller.rb
class QuestionsController < ApplicationController
before_action :set_question, only: %i[ show edit update destroy ]
before_action :set_poll
# GET /questions or /questions.json
def index
#questions = Question.all
end
# GET /questions/1 or /questions/1.json
def show
end
# GET /questions/new
def new
# #question = Question.new
#question = #poll.questions.build
3.times { #question.options.build } # 5 different options
end
# GET /questions/1/edit
def edit
end
# POST /questions or /questions.json
def create
#question = Question.new(question_params)
respond_to do |format|
if #question.save
format.html { redirect_to polls_question_url(#question), notice: "Question was successfully created." }
format.json { render :show, status: :created, location: #question }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1 or /questions/1.json
def update
respond_to do |format|
if #question.update(question_params)
format.html { redirect_to polls_question_url(#question), notice: "Question was successfully updated." }
format.json { render :show, status: :ok, location: #question }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1 or /questions/1.json
def destroy
poll_id = Question.find_by(params[:poll_id])
session[:return_to] ||= request.referer
#question.destroy
respond_to do |format|
format.html { redirect_to session.delete(:return_to), notice: "Question was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
#question = Question.find(params[:id])
end
# Only allow a list of trusted parameters through.
def question_params
params.require(:question).permit(:poll_id, :question_type, :title, :description, :randomize_selection, :voter_abstain, { option_attributes: [:question_id, :poll_id, :party_id, :title, :description] } )
end
def set_poll
#poll = poll.find_by(params[:poll_id])
end
end
routes.rb
resources :users do
resources :polls
end
resource :polls do
resources :questions
end
resource :questions do
resources :options
end
Edit:
Here is my questions form partial.
_form.html.erb
<%= form_with(model: [#Poll, question] ) do |form| %>
<% if question.errors.any? %>
<div style="color: red">
<h2><%= pluralize(question.errors.count, "error") %> prohibited this question from being saved:</h2>
<ul>
<% question.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.hidden_field :poll_id %>
</div>
<div>
<%= form.label :question_type, style: "display: block" %>
<%= form.text_field :question_type %>
</div>
<div>
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :description, style: "display: block" %>
<%= form.text_area :description %>
</div>
<div>
<%= form.label :randomize_selection, style: "display: block" %>
<%= form.check_box :randomize_selection %>
</div>
<div>
<%= form.label :voter_abstain, style: "display: block" %>
<%= form.check_box :voter_abstain %>
</div>
<div>
<%= form.fields_for :options do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
Here is the poll's show where I am rendering the forms.
show.html.erb
<p style="color: green"><%= notice %></p>
<p>
<strong>Poll Title:</strong>
<%= #poll.title %>
<%= render #poll %>
</p>
<div>
<%= link_to "Edit this poll", edit_user_poll_path(#poll) %> |
<%= link_to "Back to polls", user_polls_path %> |
<%= link_to "Destroy this poll", user_poll_path(#poll), method: :delete %>
</div>
<% if #poll.questions.any? %>
<hr>
<h2>Questions:</h2>
<%= render #poll.questions %>
<% end %>
<hr>
<h2>Add a new Question:</h2>
<%= render "questions/form", question: #poll.questions.build %>
The argument you pass to fields_for has to match the name of the assocation on the model:
<%= form.fields_for :options do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
Pay very careful attention to plurization in Rails. Its a huge part of getting Convention over Configuration to work for you instead of against you.
However there are a quite a few other problems with this code.
Constants should always be CamelCase or UPPERCASE in Ruby - you need to change class poll to class Poll and fix all the references to the class. This isn't just a matter of style since the interpreter treats identifiers that start with an uppercase letter completely differently.
You're not nesting it properly. You have a nested route but you're still treating it like a non-nested resource in your controller and docstrings.
You're passing the parent id in your params whitelist. :poll_id and :question_id should not be whitelisted. Do not pass the parent id with a hidden input. The question id is assigned by Rails - you should not trust the user to pass it.
The option should not need a poll_id. Use an indirect has_one assocation to go up the tree. This could cause a edge case where a question and its options belong to different polls.
First lets fix the models:
class Poll < ApplicationRecord
# belongs_to assocations are required by default
# adding validations will just cause duplicate error messages
validates_presence_of :title
belongs_to :user
has_many :questions, dependent: :destroy
has_many :options, through: :questions
accepts_nested_attributes_for :questions
end
class Question < ApplicationRecord
validates_presence_of :title
belongs_to :poll
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['title'].blank? }
end
class Option < ApplicationRecord
validates_presence_of :title
belongs_to :question
has_one :poll, through: :question
end
Then I would recommend that you use shallow nesting
resource :polls do
resources :questions, shallow: true
end
This creates the questions member routes (show, edit, delete) without the /polls/:poll_id prefix while the collection routes (index, create, new) are nested.
And that you set controller up as:
class QuestionsController < ApplicationController
before_action :set_question, only: %i[ show edit update destroy ]
before_action :set_poll, only: %i[ new create index ]
# GET /polls/1/questions or /polls/1/questions.json
def index
#questions = #poll.questions.all
end
# GET /questions/1 or /polls/1/questions/1.json
def show
end
# GET /polls/1/questions/new
def new
# build is just an alias of new for legacy compatibility with Rails 2...
# its about time that we ditch it
#question = #poll.questions.new
3.times { #question.options.new } # 5 different options
end
# GET /questions/1/edit
def edit
end
# POST /polls/1/questions or /polls/1/questions.json
def create
#question = #poll.questions.new(question_params)
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: "Question was successfully created." }
format.json { render :show, status: :created, location: #question }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1 or /questions/1.json
def update
respond_to do |format|
if #question.update(question_params)
format.html { redirect_to #question, notice: "Question was successfully updated." }
format.json { render :show, status: :ok, location: #question }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1 or /questions/2.json
def destroy
session[:return_to] ||= request.referer
#question.destroy
respond_to do |format|
format.html { redirect_to session.delete(:return_to), notice: "Question was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
#question = Questions.find(params[:id])
end
# Only allow a list of trusted parameters through.
def question_params
# do not write this in a single unreadable line
params.require(:question).permit(
:question_type,
:title,
:description,
:randomize_selection,
:voter_abstain,
# do not wrap hash arguments in brackets
# as it will break if/when the `permit` method is changed to use real keyword arguments
# for has_many assocations the key naming convention is also plural_attributes
options_attributes: [
:party_id,
:title,
:description
]
)
end
def set_poll
#poll = Poll.find_by(params[:poll_id])
end
end
The key difference here is that you should look up the poll by the parameter in the URL for the nested routes and create the question off the poll instance (which sets poll_id).
Added:
You're not actually using the model you initialized in your controller. If you want to render the form from a completely different action you need to initialize the instance variable there:
class PollsController < ApplicationController
def show
#question = #poll.questions.new
3.times { #question.options.new } # 5 different options ???
end
# ...
end
<%= render "questions/form", question: #question %>
And in your partial you have a sneaky little bug. Ruby is case sensitive so #poll and #Poll are actually different variables.
irb(main):049:0> #foo = "bar" => "bar"
irb(main):050:0> #Foo
=> nil
Since instance variables are auto-vivified you're just get an unexpected nil instead of an error. What you actually want is:
<%= form_with(model: [#poll, question] ) do |form| %>
Related
For my project I am trying to build a dashboard whereby an Agent can view submissions posted by a user and add a Status & Notes to each submission in order to log their own personal activity i.e they would not be changing the actual record, just leaving private notes against it. In order to do this I have created a join table with both Agent id and Submission id as well as Status and Notes columns.
I have managed to create an index view that shows submissions data with 2 form fields at the end of each line from my join table which are called Status and Notes... the problem is when I update these fields they do not get saved to my jointable.
Form on index view
<%= form_with(model: submission, local: true) do |form| %>
<% form.fields_for :agent_activities do |act| %>
<td> <div class="field">
<%= act.text_field :Status %>
</div>
</td>
<td> <div class="field">
<%= act.text_field :Notes %>
</div>
</td>
<td>
<div class="actions">
<%= form.submit %>
</div>
</td>
<% end %>
<% end %>
Model associations in rb files
class Submission < ApplicationRecord
belongs_to :user, :optional => true
belongs_to :location, :optional => true
has_many :agent_activities
end
class AgentActivity < ApplicationRecord
belongs_to :submission, :optional => true #has submission_id
foreign key in table
belongs_to :agent, :optional => true #has agent_id foreign key in
table
end
Controller:
class SubmissionsController < ApplicationController
before_action :set_submission, only: [:show, :edit, :update, :destroy]
def index
#submissions = Submission.where(:user_id => current_user.id)
end
def show
end
def new
#submission = Submission.new
end
def edit
end
# POST /submissions
# POST /submissions.json
def create
#submission = Submission.new(submission_params.merge(user_id: current_user.id))
respond_to do |format|
if #submission.save
# Tell the UserMailer to send a welcome email after save
NewSubmissionMailer.submission_email(#submission).deliver_now
NewSubmissionMailer.matching_agents_email(#submission).deliver_now
format.html { redirect_to #submission, notice: 'Submission was successfully created.' }
format.json { render :show, status: :created, location: #submission }
else
format.html { render :new }
format.json { render json: #submission.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /submissions/1
# PATCH/PUT /submissions/1.json
def update
respond_to do |format|
if #submission.update(submission_params)
format.html { redirect_to #submission, notice: 'Submission was successfully updated.' }
format.json { render :show, status: :ok, location: #submission }
else
format.html { render :edit }
format.json { render json: #submission.errors, status: :unprocessable_entity }
end
end
end
# DELETE /submissions/1
# DELETE /submissions/1.json
def destroy
#submission.destroy
respond_to do |format|
format.html { redirect_to submissions_url, notice: 'Submission was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_submission
#submission = Submission.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def submission_params
params.require(:submission).permit(:First_Name, :Last_Name, :Phone, :Email, :Desired_Location, :number_of_beds, :number_of_occupants, :Rent_price_per_month_gbp, :Max_move_in_date, :Tenant_Occupation, :Contact_me_on, :Furnished, :Current_Address, :Property_Requirements)
end
end
Not sure what im missing here :/
UPDATE BASED OFF #TOM ANSWER
New controller params:
def submission_params
params.require(:submission).permit(:First_Name, :Last_Name, :Phone, :Email, :Desired_Location, :number_of_beds, :number_of_occupants, :Rent_price_per_month_gbp, :Max_move_in_date, :Tenant_Occupation, :Contact_me_on, :Furnished, :Current_Address, :Property_Requirements, agent_activities_attributes: [:id, :Status, :Notes, :_destroy])
end
end
New Submission Model rb:
class Submission < ApplicationRecord
belongs_to :user, :optional => true
belongs_to :location, :optional => true
has_many :agent_activities
accepts_nested_attributes_for :agent_activities
end
Index.html.erb
<%= form_with(model: submission, local: true) do |form| %>
<% form.fields_for :agent_activities, #submission.agent_activities.build do |act| %>
<td> <div class="field">
<%= act.text_field :Status %>
</div>
</td>
<td> <div class="field">
<%= act.text_field :Notes %>
</div>
</td>
<td>
<div class="actions">
<%= form.submit %>
</div>
</td>
<% end %>
On your Submission model add: accepts_nested_attributes_for :agent_activities (accepts_nested_attributes_for documentation) This will let Rails know that your form is going to be supplying fields for an associated model.
Once that is added Rails will be supplying a key in params agent_activities_attributes in your strong params we can add: .permit(..., agent_activities_attributes: [:id, :Status, :Notes, :_destroy]. The :_destroy key is only needed if you plan on having allow_destroy: true on the nested attribute call.
One side note: Capitalized names (Status, Notes, etc) are normally reserved for constants in Ruby. You may want to look into changing your attribute column names to lowercase.
I'm making a invoice application where i have the #invoice form and inside it i have nested forms for customers, products, and company information. The products form is working fine in all views but the customer form isn't. When i fill in the customer information and create a new invoice it works. But when i try to edit that invoice the entire form is gone.
invoice/_form
<%= form_for #invoice do |f| %>
<% if #invoice.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#invoice.errors.count, "error") %> prohibited this invoice from being saved:</h2>
<ul>
<% #invoice.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.fields_for :customer do |customer| %>
<div class="field">
<%= customer.label 'Bedrijfsnaam ontvanger' %><br/>
<%= customer.text_field :company_name, placeholder: 'bedrijfsnaam', class: 'form-control' %>
</div>
<div class="field">
<%= customer.label 'Adres ontvanger' %><br>
<%= customer.text_field :address_line_1, placeholder: 'adres ontvanger', class: 'form-control' %>
</div>
<div class="field">
<%= customer.label 'Postcode & stad' %><br>
<%= customer.text_field :zip_code, placeholder: '1234AB Rotterdam', class: 'form-control' %>
</div>
<% end %>
Invoices_controller.rb
class InvoicesController < ApplicationController
before_action :set_invoice, only: [:show, :edit, :update, :destroy]
# GET /invoices
# GET /invoices.json
def index
#invoices = Invoice.all
end
# GET /invoices/1
# GET /invoices/1.json
def show
end
# GET /invoices/new
def new
#invoice = Invoice.new
#invoice.products.build
#invoice.build_customer
end
# GET /invoices/1/edit
def edit
end
# POST /invoices
# POST /invoices.json
def create
#invoice = Invoice.new(invoice_params)
respond_to do |format|
if #invoice.save
format.html { redirect_to #invoice, notice: 'Invoice was successfully created.' }
format.json { render :show, status: :created, location: #invoice }
else
format.html { render :new }
format.json { render json: #invoice.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /invoices/1
# PATCH/PUT /invoices/1.json
def update
respond_to do |format|
if #invoice.update(invoice_params)
format.html { redirect_to #invoice, notice: 'Invoice was successfully updated.' }
format.json { render :show, status: :ok, location: #invoice }
else
format.html { render :edit }
format.json { render json: #invoice.errors, status: :unprocessable_entity }
end
end
end
# DELETE /invoices/1
# DELETE /invoices/1.json
def destroy
#invoice.destroy
respond_to do |format|
format.html { redirect_to invoices_url, notice: 'Invoice was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_invoice
#invoice = Invoice.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def invoice_params
params.require(:invoice).permit(:number, :currency, :date, :duedate, :btwtotal,
:subtotal, :total, :footer, customers_attributes: [:id, :company_name, :address_line_1, :zip_code, :_destroy],
companies_attributes: [:id, :btw_number, :iban_number, :kvk_number, :company_name, :_destroy],
products_attributes: [:id, :quantity, :description, :unitprice, :btw, :total])
end
end
Invoice.rb - (model)
class Invoice < ActiveRecord::Base
has_one :company
has_one :customer
has_many :products
accepts_nested_attributes_for :customer, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :products, reject_if: :all_blank, allow_destroy: true
validates :number, :currency, :date, :duedate, :btwtotal, :subtotal, :total, presence: true
end
In the invoices controller try changing customers_attributes to customer_attributes in your strong params:
customer_attributes: [:id, :company_name, :address_line_1, :zip_code, :_destroy]
I suspect this is an issue where your customer nested attributes are not being properly saved, so when you go to the edit view that part of the form isn't being rendered because there isn't any customer saved for your invoice
I'm struggling to figure out how to set up my rails evaluation model so that users can use it to leave feedback on other users.
I outlined the key part of my problem in this post:
Rails - feedback on specific users, how to set up the form to identify relevant users
The suggestion I received from that was to set up the model as follows:
User.rb
has_many :given_evaluations, foreign_key: :evaluator_id, dependent: :destroy, class_name: Evaluation
has_many :received_evaluations, foreign_key: :evaluatee_id, dependent: :destroy, class_name: Evaluation
Evaluation.rb
belongs_to :evaluator, foreign_key: :evaluator_id, class_name: User
belongs_to :evaluatee, foreign_key: :evaluatee_id, class_name: User
Evaluation Controller
class EvaluationsController < ApplicationController
before_action :set_evaluation, only: [:show, :edit, :update, :destroy]
# before_filter :get_user, only: [:show, :edit, :update, :destroy]
# GET /evaluations
# GET /evaluations.json
def index
# #evaluations = Evaluation.all
#given_evaluations = current_user.given_evaluations
#received_evaluations = current_user.received_evaluations
end
# GET /evaluations/1
# GET /evaluations/1.json
def show
# #received_evaluations = #user.received_evaluations
#evaluation = current_user.received_evaluations.find_by(id: params[:id]) || current_user.given_evaluations.find(params[:id])
# #received_evaluation = current_user.received_evaluations.find params[:id]
end
# GET /evaluations/new
def new
#evaluation = Evaluation.new
end
# GET /evaluations/1/edit
def edit
end
# POST /evaluations
# POST /evaluations.json
def create
# #evaluation = Evaluation.new(evaluation_params)
#evaluation = current_user.given_evaluations.build(evaluation_params)
respond_to do |format|
if #evaluation.save
format.html { redirect_to #evaluation, notice: 'Evaluation was successfully created.' }
format.json { render :show, status: :created, location: #evaluation }
else
format.html { render :new }
format.json { render json: #evaluation.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /evaluations/1
# PATCH/PUT /evaluations/1.json
def update
current_user.given_evaluations.find(params[:id])
respond_to do |format|
if #evaluation.update(evaluation_params)
format.html { redirect_to #evaluation, notice: 'Evaluation was successfully updated.' }
format.json { render :show, status: :ok, location: #evaluation }
else
format.html { render :edit }
format.json { render json: #evaluation.errors, status: :unprocessable_entity }
end
end
end
# DELETE /evaluations/1
# DELETE /evaluations/1.json
def destroy
current_user.given_evaluations.find(params[:id])
#evaluation.destroy
respond_to do |format|
format.html { redirect_to evaluations_url, notice: 'Evaluation was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_evaluation
#evaluation = Evaluation.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def evaluation_params
params[:evaluation].permit(:overall_score, :project_score, :personal_score, :remark, :work_again?, :continue_project?, :evaluatee_id)
end
end
Evaluation form
<%= simple_form_for(#evaluation) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.select :evaluatee_id, User.all.map{|u| [u.formal_name, u.id]} %>
<%= f.input :overall_score, collection: 1..10, autofocus: true, :label => "How do you rate this project experience (1 being did not meet expectations - 10 being met all expectations) ?" %>
<%= f.input :continue_project?, as: :boolean, checked_value: true, unchecked_value: false, :label => "Do you intend to continue working on the project?" %>
<%= f.input :remark, as: :text, :label => "Evaluate your experience", :input_html => {:rows => 10} %>
</div>
<div class="form-actions">
<%= f.button :submit %>
Evaluation show view
<% #received_evaluations.each do |receval| %>
<div id="portfolioFiltering" class="masonry-wrapper row">
<%= receval.remark %>
<%#= eval.personal_score %>
<small><%= receval.created_at %></small>
</div>
<% end %>
Alternative attempt at evaluation show view
<% #given_evaluations.each do |receval| %>
<div id="portfolioFiltering" class="masonry-wrapper row">
<%= receval.remark %>
<%#= eval.personal_score %>
<small><%= receval.created_at %></small>
</div>
<% end %>
The problem I'm having now is that regardless of whether I try to show given evaluation or received evaluation in the show, I get an error message that says:
undefined method `each' for nil:NilClass
I can't figure out how to setup the model so that a user can evaluate another user. I want to show each user's received evaluation on their respective show page. i can't figure out what's going wrong. I can see from the console that a user has received evaluations as:
=> #<Evaluation id: 8, evaluatee_id: 34, overall_score: 4, project_score: 5, personal_score: 5, remark: "jhjkhjhjkhkjhjkhjhkhjhkj", work_again?: nil, continue_project?: nil, created_at: "2016-06-12 21:52:53", updated_at: "2016-06-12 21:52:53", evaluator_id: 34>
There is an entry for the user I'm working with. However, I can't find a way to show that evaluation.
The most immediate problem I could see is that the instance variables: #given_evaluations and #received_evaluations are not being set in your show actions and the commented portions are using the ActiveRecord#find method, which would return one instance therefore the reason you couldn't loop through the evaluations.
I think a better place to show all your user's evaluation is in the index action, as you're doing already, you may move the current logic for the show to the index view.
To show each user's received evaluation on the show action, you would do:
#evaluation = current_user.received_evaluations.find(params[:id])
Then in your show view, you should have something like:
<div id="portfolioFiltering" class="masonry-wrapper row">
<%= #evaluation.remark %>
<%= #evaluation.personal_score %>
<small><%= #evaluation.created_at %></small>
</div>
I have an "undefined method" issue with my app, and don't find where it comes from :(.
In my app, i have 4 models :
Deal, Pool (which belongs to deal), Facility (which belongs to pool), Facilityschedule (which belongs to Facility).
class Deal < ActiveRecord::Base
has_many :pools, :dependent => :destroy
accepts_nested_attributes_for :pools, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
end
class Pool < ActiveRecord::Base
belongs_to :deal
has_many :facilities, :dependent => :destroy
accepts_nested_attributes_for :facilities, :allow_destroy => true
end
class Facility < ActiveRecord::Base
belongs_to :pool
has_many :facilityschedules, :dependent => :destroy
accepts_nested_attributes_for :facilityschedules, :reject_if => lambda { |a| a[:date].blank? }, :allow_destroy => true
end
class Facilityschedule < ActiveRecord::Base
belongs_to :facility
end
I have a partial form which allows the user to create all of these :
<%= form_for(#deal) do |f| %>
<% if #deal.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#deal.errors.count, "error") %> prohibited this deal from being saved:</h2>
<ul>
<% #deal.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name, "Deal name"%>
<%= f.text_field :name %>
<br/>
</div>
<div>
<%= f.fields_for :pools do |builder|%>
<%= builder.label :name, "Pool name" %>
<%= builder.text_field :name, :rows => 3 %>
<%= builder.check_box :_destroy %>
<%= builder.label :_destroy, "Remove Pool" %>
<br/>
<%= builder.fields_for :facilities do |fbuilder|%>
<%= fbuilder.label :name, "Facility name" %>
<%= fbuilder.text_field :name, :rows => 3 %>
<%= fbuilder.check_box :_destroy %>
<%= fbuilder.label :_destroy, "Remove Facility" %>
<br/>
<%= fbuilder.fields_for :facilitieschedules do |sbuilder|%>
<%= sbuilder.label :date, "Schedule" %>
<%= sbuilder.text_field :date, :rows => 3 %>
<%= sbuilder.check_box :_destroy %>
<%= sbuilder.label :_destroy, "Remove Schedule" %>
<br/>
</div>
<% end %>
<% end %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And finally, i have my Deal controller where the issue is located (new action) :
class DealsController < ApplicationController
before_action :set_deal, only: [:show, :edit, :update, :destroy]
# GET /deals
# GET /deals.json
def index
#deals = Deal.all
end
# GET /deals/1
# GET /deals/1.json
def show
end
# GET /deals/new
def new
#deal = Deal.new
2.times do
pool = #deal.pools.build
2.times do
**facility = #pool.facilities.build**
1. times { facility.facilityschedules.build }
end
end
end
# GET /deals/1/edit
def edit
end
# POST /deals
# POST /deals.json
def create
#deal = Deal.new(deal_params)
respond_to do |format|
if #deal.save
format.html { redirect_to #deal, notice: 'Deal was successfully created.' }
format.json { render :show, status: :created, location: #deal }
else
format.html { render :new }
format.json { render json: #deal.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /deals/1
# PATCH/PUT /deals/1.json
def update
respond_to do |format|
if #deal.update(deal_params)
format.html { redirect_to #deal, notice: 'Deal was successfully updated.' }
format.json { render :show, status: :ok, location: #deal }
else
format.html { render :edit }
format.json { render json: #deal.errors, status: :unprocessable_entity }
end
end
end
# DELETE /deals/1
# DELETE /deals/1.json
def destroy
#deal.destroy
respond_to do |format|
format.html { redirect_to deals_url, notice: 'Deal was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_deal
#deal = Deal.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def deal_params
params.require(:deal).permit(:name, pools_attributes: [:id, :name, :number, :deal_id, :_destroy, facilities_attributes: [:id, :name, :pool_id, :_destroy, facilityschedules_attributes: [:id, :facility_id, :date, :_destroy]]])
end
end
When i try to create a new deal, the following error message pops up "undefined method `facilities' for nil:NilClass" (in bold in the Deal controller, above).
What am i doing wrong?
Many thanks and have a good week end :)
# GET /deals/new
def new
#deal = Deal.new
2.times do
pool = #deal.pools.build
2.times do
**facility = #pool.facilities.build**
1. times { facility.facilityschedules.build }
end
end
end
In the code above you have never set the #pool variable, but instead set pool.
Undefined method for Nil::NilClass is never about not having declared the method, it's that you're trying to call it on a nil object, which is an object of NilClass.
There are also some strange patterns in this method. I've never seen the ** around code in Rails. Are you trying to comment out the line?
Would this work better?
# GET /deals/new
def new
#deal = Deal.new
2.times do
#pool = #deal.pools.build
2.times do
facility = #pool.facilities.build
facility.facilityschedules.build
end
end
end
I am building a book repository in rails and I need to be able to add an author inside a book create form that passes the author post into the list of authors once the book has been added. In the same book creation resource I have already created the has_many: authors in the book.rb file and in the author.rb file I have created the belongs_to: author and that works fine I can select the books that author might have created with the following setup:
book.rb
class Book < ActiveRecord::Base
has_attached_file :jacket_cover, :styles => { :medium => "300x300>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
validates_attachment_content_type :jacket_cover, :content_type => /\Aimage\/.*\Z/
validates :jacket_cover, :title, :synopsis, :body, presence: true
belongs_to :author
scope :available, ->{ where(available: true) }
scope :unavailable, ->{ where(available: [nil, false]) }
end
author.rb
class Author < ActiveRecord::Base
has_many :books
end
books_controller.rb
class BooksController < ApplicationController
before_action :set_book, only: [:show, :edit, :update, :destroy]
def index
#books = Book.all
end
def show
end
# GET /books/new
def new
#book = Book.new
end
# GET /books/1/edit
def edit
end
def create
#book = Book.new(book_params)
respond_to do |format|
if #book.save
format.html { redirect_to #book, notice: 'Book was successfully created.' }
format.json { render action: 'show', status: :created, location: #book }
else
format.html { render action: 'new' }
format.json { render json: #book.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #book.update(book_params)
format.html { redirect_to #book, notice: 'Book was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #book.errors, status: :unprocessable_entity }
end
end
end
def destroy
#book.destroy
respond_to do |format|
format.html { redirect_to books_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_book
#book = Book.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def book_params
params.require(:book).permit(:title, :synopsis, :body, :jacket_cover)
end
end
authors_controller.rb
class AuthorsController < ApplicationController
before_action :set_author, only: [:show, :edit, :update, :destroy]
def index
#authors = Author.all
end
def show
end
def new
#author = Author.new
end
# GET /authors/1/edit
def edit
end
def create
#author = Author.new(author_params)
respond_to do |format|
if #author.save
format.html { redirect_to #author, notice: 'Author was successfully created.' }
format.json { render action: 'show', status: :created, location: #author }
else
format.html { render action: 'new' }
format.json { render json: #author.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #author.update(author_params)
format.html { redirect_to #author, notice: 'Author was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #author.errors, status: :unprocessable_entity }
end
end
end
def destroy
#author.destroy
respond_to do |format|
format.html { redirect_to authors_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_author
#author = Author.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def author_params
params.require(:author).permit(:name, :biography, :books_ids => [] )
end
end
This allows me to create the books and the authors which is fine but I am now looking to nest the author create into my book create too.
form for book create
<%= simple_form_for(#book, :html => { :multipart => true }) do |f| %>
<%= f.error_notification %>
<div class="inputs">
<%= f.file_field :jacket_cover %>
<%= f.input :title %>
<%= f.input :synopsis %>
<%= f.input :body %>
</div>
<div class="actions">
<%= f.button :submit %>
</div>
<% end %>
form for author create
<%= simple_form_for(#author) do |f| %>
<%= f.error_notification %>
<div class="inputs">
<%= f.input :name %>
<%= f.association :books,
as: :check_boxes,
value_method: :id,
label: 'Books' %>
<%= f.input :biography %>
</div>
<div class="actions">
<%= f.button :submit %>
</div>
<% end %>
Is it possible to nest another resource into an already created resource so adding an author create inside a book create page?
Yes,you need accepts_nested_attributes_for and simple_fields_for helper provided for the simple_form_for(as you are using simple_form_for)
Step #1
In your Book model,you should add accepts_nested_attributes_for :author
Step #2
Modifying your new method of books_controller.rb
As you have belongs_to :author in your Book model,your new method of your BooksController would be
def new
#book = Book.new
#book.build_author #This is very important
end
Step #3
Your book_params method should be modified to
def book_params
params.require(:book).permit(:title, :synopsis, :body, :jacket_cover,author_attributes: [:name,:biography,..,..])
end
Step #4
Finally,your form for book create would be something like this
<%= simple_form_for(#book, :html => { :multipart => true }) do |f| %>
<%= f.error_notification %>
<div class="inputs">
<%= f.file_field :jacket_cover %>
<%= f.input :title %>
<%= f.input :synopsis %>
<%= f.input :body %>
</div>
<%= f.simple_fields_for :author do |a| %>
... author fields...
....................
<% end %>
<div class="actions">
<%= f.button :submit %>
</div>
<% end %>
Is it possible to nest another resource into an already created resource so adding an author create inside a book create page?
Yes.
You probably want a longer answer than that though.
You need to get nested attributes sorted out.
In Book model:
has_one :author, dependent: :destroy
accepts_nested_attributes_for :authors
In BooksController :new add line
#book = Author.new
#book.build_author
And in form for #book:
<%= form_for(#book, :html => { :multipart => true }) do |f| %>
<%= f.error_notification %>
<div class="inputs">
#book fields
</div>
<%= f.fields_for :author do |author| %>
#author fields
<%= f.button :submit %>
</div>
<% end %>
And don't forget to modify book_params method as shown in answer by Rich Peck.
Your question is somewhat verbose, but I'll detail what you need to know
--
Nested Objects
Is it possible to nest another resource into an already created resource so adding an author create inside a book create page?
Rails is built on top of Ruby (an object orientated language). This means Rails is object orientated too, and so if you wanted to create an author at the same time as creating a book (this is only applicable for create), you'll want to use the accepts_nested_attributes_for directive for your model:
#app/models/book.rb
Class Book < ActiveRecord::Base
has_one :author
accepts_nested_attributes_for :author
end
#app/controllers/books_controller.rb
Class BooksController < ApplicationController
def new
#book = Book.new
#book.build_author #-> this will be authors.build if multiple
end
def create
#book = Book.new(book_params)
#book.save
end
private
def book_params
params.require(:book).permit(:title, :synopsis, :body, author_attributes: [:biography])
end
end
This will allow you to create the following (using form_for for simplicity sake):
#app/views/books/new.html.erb
<%= form_for #book do |f| %>
<%= f.text_field :title %>
<%= f.text_field :synopsis %>
<%= f.text_field :body %>
<%= f.fields_for :author do |a| %>
<%= a.text_field :biography %>
<% end %>
<%= f.submit %>
<% end %>
This will create the book record & associated author record, too