it seems like a lot of people posted this problem as here but the answer there, is not my case I guess.
in my models I have
class Project < ActiveRecord::Base
has_many :questions, dependent: :destroy
validates :name, presence: true, uniqueness: true
validates :category_id, presence: true
end
and
class Question < ActiveRecord::Base
belongs_to :project
validates :question, presence: true
validates :question_type, presence: true
validates :project_id, presence: true
QUESTIONS_TYPES = ['Single', 'Multiple', 'A text']
end
<%= form_for(#question) do |f| %>
tha form:
<div class="field">
<%= f.label :project_id %><br>
<%= f.select :project_id, #project_options %>
</div>
<div class="field">
<%= f.label :question %><br>
<%= f.text_field :question %>
</div>
<div class="field">
<%= f.label :question_type %><br>
<%= f.select :question_type, Question::QUESTIONS_TYPES %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The controller
class QuestionsController < ApplicationController
before_action :set_question, only: [:show, :edit, :update, :destroy]
before_action :set_projects, only: [:new, :edit]
.
.
def create
#question = Question.new(question_params)
.
.
end
.
.
private
# Use callbacks to share common setup or constraints between actions.
def set_question
#question = Question.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def question_params
params.require(:question).permit(:question, :question_type, :project_id)
end
def set_projects
#project_options = Project.all.map{|p| [p.name, p.id]}
end
end
The error
undefined method `empty?' for nil:NilClass
Extracted source (around line #16):
<div class="field">
<%= f.label :project_id %><br>
<%= f.select :project_id, #project_options %>
</div>
the post answer I mentioned before says it should be project_id in the form, but it is correct in my code. In my case the error raises only if I have some empty fields otherwise it goes well. could be something wrong with validations?
Thanks!!
Yes it seems it has validation issue and could cot create/update the record.
But the error is: your create and update action probably have a else block in the controller to handle the failed cases. make sure you initiate #project_options there in the else block. What happens is it cant create the record hence tries to render the new/edit form directly(not executing new/edit methods so #project_options is not initialized). put something in your controller in create action like:
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: 'Question was successfully updated.' }
format.json { head :no_content }
else
format.html do
set_projects #need to call set_projects method here.
render action: "new"
end
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
You should have same thing in your update action as well
The error you have is that empty? method is called on nil. The two objects here are #question and #project_options. Check if you have assigned the proper values to these instance variables in your controller.
Related
I feel like this should be an easy thing to do in Rails, but all of the examples of nested forms in Rails do not take into account the fact that most nested forms also need to pass the current_user when creating new objects through a nested form.
The only way I can get this to work at the moment is by passing a hidden field such as <%= form.hidden_field :user_id, value: current_user.id %>.
For my specific example, I have a model called "Result" that has many "Lessons" and I'd like to create new lessons through the Result form without passing a hidden :user_id.
This seems unsafe because someone could edit that hidden field in the browser and then submit the form thus associating the submission with a different user. The current_user.id seems like the type of thing you don't want to embed in the html as a hidden field.
So how do you create the association between the nested objects and the current_user without putting that hidden field in the form?
FYI, I'm using the GoRails nested form with stimulus style javascript to add and remove lessons from the result form. (Here's the source code for that example.) Here are the relevant parts of my code:
models/result.rb
class Result < ApplicationRecord
belongs_to :user
has_many :lessons, inverse_of: :result
accepts_nested_attributes_for :lessons, reject_if: :all_blank, allow_destroy: true
end
models/lesson.rb
class Lesson < ApplicationRecord
belongs_to :user
belongs_to :result
end
controllers/results_controller.rb
class ResultsController < ApplicationController
before_action :set_result, only: [:show, :edit, :update, :destroy]
def new
#result = Result.new
#result.lessons.new
end
def create
#result = current_user.results.new(result_params)
respond_to do |format|
if #result.save
format.html { redirect_to #result, notice: 'Result was successfully created.' }
else
format.html { render :new }
end
end
end
private
def set_result
#result = Result.find(params[:id])
end
def result_params
params.require(:result).permit(:prediction_id, :post_mortem, :correct,
lessons_attributes: [:user_id, :id, :summary, :_destroy])
end
end
controllers/lessons_controller.rb
class LessonsController < ApplicationController
before_action :set_lesson, only: [:show, :edit, :update, :destroy]
# GET /lessons/new
def new
#lesson = Lesson.new
end
def create
#lesson = current_user.lessons.new(lesson_params)
respond_to do |format|
if #lesson.save
format.html { redirect_to #lesson, notice: 'Lesson was successfully created.' }
else
format.html { render :new }
end
end
end
private
def set_lesson
#lesson = Lesson.find(params[:id])
end
def lesson_params
params.require(:lesson).permit(:result_id, :summary)
end
end
views/results/_form.html.erb
<%= form_with(model: result, local: true) do |form| %>
<h3>Lessons</h3>
<div data-controller="nested-form">
<template data-target="nested-form.template">
<%= form.fields_for :lessons, Lesson.new, child_index: 'NEW_RECORD' do |lesson| %>
<%= render "lesson_fields", form: lesson %>
<% end %>
</template>
<%= form.fields_for :lessons do |lesson| %>
<%= render "lesson_fields", form: lesson %>
<% end %>
<div class="pt-4" data-target="nested-form.links">
<%= link_to "Add Lesson", "#",
data: { action: "click->nested-form#add_association" } %>
</div>
</div>
<div class="form-submit">
<%= form.submit "Save" %>
</div>
<% end %>
views/results/_lesson_fields.html.erb
<%= content_tag :div, class: "nested-fields", data: { new_record: form.object.new_record? } do %>
# This hidden field seems unsafe!
<%= form.hidden_field :user_id, value: current_user.id %>
<div class="pb-8">
<%= form.text_area :summary %>
<%= link_to "Remove", "#",
data: { action: "click->nested-form#remove_association" } %>
</div>
<%= form.hidden_field :_destroy %>
<% end %>
I'm sure this is a common problem in Rails but I can't find any tutorials online that have the user_id as a part of the nested fields example. Any help is much appreciated!
Personally, since setting the current_user id is something the controller should care about, I would iterate over all the lessons and set the user_id value there.
def create
#result = current_user.results.new(result_params)
#result.lessons.each do |lesson|
lesson.user ||= current_user if lesson.new_record?
end
... the rest ...
Having a hidden field is a security risk, someone could edit it. I also don't like changing the params hash.
I don't think there is a great way to handle this automatically outside of the view. You would either have to inject the value unto the params or possible have a use default on the user association in Lesson that sets it from the Record's user (belongs_to :user, default: -> { result.user }). In these scenarios, I generally move outside of the default Rails flow and use a PORO, Form Object, service object, etc.
build form like this
<%= form.fields_for :lessons, lesson_for_form(current_user.id) do |lesson| %>
<%= render "lesson_fields", form: lesson %>
<% end %>
remove hidden user_id field you have added
update your result.rb file
class Result < ApplicationRecord
def lesson_for_form(user_id)
collection = lessons.where(user_id: user_id)
collection.any? ? collection : lessons.build(user_id: user_id)
end
end
My goal is to when adding a new product with the new product form, to have an input where one can add a list of emails separated by a space. The list of emails in this string field would be saved as an array of emails in the email_list array attribute of the Product model. This way each product has many emails. (later an email will be sent to these users to fill out questionaire, once a user fills it out there name will be taken off this list and put on completed_email_list array.
I am relatively new to rails, and have a few questions regarding implementing this. I am using postgresql, which from my understanding I do not need to serialize the model for array format because of this. Below is what I have tried so far to implement this. These may show fundamental flaws in my thinking of how everything works.
My first thinking was that I can in my controllers create action first take params[:email].split and save that directly into the email_list attribute (#product.email_list = params[:email].split. It turns out that params[:email] is always nil. Why is this? (this is a basic misunderstanding I have)(I put :email as accepted param).
After spending a long time trying to figure this out, I tried the following which it seems works, but I feel this is probably not the best way to do it (in the code below), which involves creating ANOTHER attribute of string called email, and then splitting it and saving it in the email_list array :
#product.email_list = #product.email.split
What is the best way to actually implement this? someone can clear my thinking on this I would be very grateful.
Cheers
Products.new View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.input :email %>
<%= f.button :submit %>
<%end %>
Products Controller
class ProductsController < ApplicationController
before_action :find_product, only: [:show, :edit, :update, :destroy]
def index
if params[:category].blank?
#products= Product.all.order("created_at DESC")
else
#category_id=Category.find_by(name: params[:category]).id
#products= Product.where(:category_id => #category_id).order("created_at DESC")
end
end
def new
#product=current_user.products.build
#categories= Category.all.map{|c| [c.name, c.id]}
end
def show
end
def edit
#categories= Category.all.map{|c| [c.name, c.id]}
end
def update
#product.category_id = params[:category_id]
if #product.update(product_params)
redirect_to product_path(#product)
else
render 'new'
end
end
def destroy
#product.destroy
redirect_to root_path
end
def create
#product=current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = #product.email.split
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:product).permit(:title, :description, :category_id, :video, :thumbnail,:email, :email_list)
end
def find_product
#product = Product.find(params[:id])
end
end
To solve your original issue
#product.email_list = params[:email].split. It turns out that params[:email] is always nil
:email is a sub key of :product hash, so it should be:
#product.email_list = params[:product][:email].split
Demo:
params = ActionController::Parameters.new(product: { email: "first#email.com last#email.com" })
params[:email] # => nil
params[:product][:email] # => "first#email.com last#email.com"
I'd say that what you have is perfectly fine, except for the additional dance that you're doing in #product.email_list=#product.email.split, which seems weird.
Instead, I'd have an emails param in the form and an #emails= method in the model (rather than email and #email=):
def emails=(val)
self.email_list = val.split
end
Alternatively, you could do that in the controller rather than having the above convenience #emails= method, similar to the way you're handling the category_id:
#product = current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = product_params[:emails].split
Because you need validations on your emails and to make it cleaner I would create an email table, make Product table accept Email attribues and use cocoon gem to have a nice dynamic nested form with multiple emails inputs.
1) models
class Product < ActiveRecord::Base
has_many :emails, dependent: :destroy
accepts_nested_attributes_for :emails, reject_if: :all_blank, allow_destroy: true
end
class Email < ActiveRecord::Base
belong_to :product
validates :address, presence: true
end
2) Controller
class ProductsController < ApplicationController
def new
#product = current_user.products.build
end
def create
#product = current_user.products.build(product_params)
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:project).permit(:title, :description, :category_id, :video, :thumbnail, emails_attributes: [:id, :address, :_destroy])
end
end
3) View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.association :category %>
<div id="emails">
<%= f.simple_fields_for :emails do |email| %>
<%= render 'emails_fields', f: email %>
<div class="links">
<%= link_to_add_association 'add email', f, :emails %>
</div>
<%= end %>
</div>
<%= f.button :submit %>
<% end %>
In your _emails_fields partial:
<div class="nested-fields">
<%= f.input :address %>
<%= link_to_remove_association "Remove email", f %>
</div>
Then setup cocoon's gem and javascript and you'll be good.
Reference: https://github.com/nathanvda/cocoon
I'm working on an app that has and agency which has many branches and both the agency and the branches can have addresses. I have created the models for them as shown below:
# agency.rb
has_many :branches
has_one :address
accepts_nested_attributes_for :branches, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :address, reject_if: :all_blank, allow_destroy: true
# branch.rb
belongs_to :agency
has_one :address
accepts_nested_attributes_for :address, reject_if: :all_blank
# address.rb
belongs_to :agency
belongs_to :branch
I have an agency form which should allow me to create/edit an agency and it's address as well as add/edit/delete branches and their addresses too.
Here is the agency form:
<%= form_for(#agency) do |f| %>
<div class="box-body">
# errors and messages etc etc ...
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name, class: 'form-control' %>
</div>
# more agency fields
<%= f.fields_for :address do |address| %>
<%= render 'addresses/address_fields', f: address %>
<% end %>
#other fields which all work fine
<div id="branches">
<%= f.fields_for :branches do |branch| %>
<%= render 'branch_fields', f: branch %>
<% end %>
<div class="links">
<%= link_to_add_association 'add branch', f, :branches %>
</div>
</div>
</div>
<%= f.submit 'Save Changes', class: 'btn btn-primary' %>
<% end %>
Here are the branch fields (partial form):
# branch name ...
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
# I will explain this bit below
<% if f.object.new_record? %>
<% f.object.build_address %>
<% end %>
###
<%= f.fields_for :address do |address| %>
<%= render 'addresses/address_fields', f: address %>
<% end %>
# other branch fields that work fine
<%= link_to_remove_association "remove branch", f %>
And here is the agencies controller:
class AgenciesController < ApplicationController
before_action :authenticate_user!
before_action :set_agency, only: [:show, :edit, :update]
# before_action :build_address, only: [:new, :edit]
def index
#agencies = Agency.all
end
def show
end
def create
#agency = Agency.new(agency_params)
respond_to do |format|
if #agency.save
format.html { redirect_to #agency, notice: 'Agency was successfully created.' }
format.json { render :show, status: :created, location: #agency }
else
format.html { render :new }
format.json { render json: #agency.errors, status: :unprocessable_entity }
end
end
end
def new
#agency = Agency.new
#agency.build_address
#agency.build_domain_name
#agency.branches.build
#agency.services.build
# #agency.branches.build_addresses
# #agency.branches.each do |branch|
# branch.build_address
# end
end
def edit
end
def update
respond_to do |format|
if #agency.update(agency_params)
format.html { redirect_to #agency, notice: 'Agency was successfully updated.' }
format.json { render :show, status: :ok, location: #agency }
else
format.html { render :edit }
format.json { render json: #agency.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_agency
#agency = Agency.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def agency_params
params.require(:agency).permit(:name, :logo, :description, :phone_number, :alt_phone_number, :email, :alt_email,
services_attributes:
[:id, :name, :description, :_destroy],
address_attributes:
[:id, :first_line, :second_line, :third_line, :city_town, :post_code, :_destroy],
domain_name_attributes:
[:id, :domain],
branches_attributes:
[:id, :name, :description, :phone_number, :alt_phone_number, :_destroy, :email, :alt_email,
services_attributes:
[:id, :name, :description, :_destroy],
address_attributes:
[:id, :first_line, :second_line, :third_line, :city_town, :post_code, :_destroy]
]
)
end
end
Apologies for the lengthy code.
The problem I was facing initially was that when I was adding a new branch, the address fields were not showing up. After some digging, I found a question on SO where someone suggested running f.object.build_address
just before calling the fields for the branch. This worked fine for creating a new branch on the agency form but after saving, and returning to the edit agency page, the address fields for the branch were blank. However, the address was saved correctly to teh database. I think this was caused bu the build_address line overriting the content when editing which is why I attempted to wrap it in the if loop but I'm almost certain this is wrong.
How can I correctly build the nested branch address attributes in the controller so that the fields are shown correctly when adding a new agency and or branch? And how can I ensure that the saved values are displayed correctly when I come back to edit the agency and branch(es) later.
I have done a lot of reading and digging and I cant see any of the normal issues like wrong tags used (<%= / <%) to no avail. Any help would be most appreciated.
Try with these modifications:
<%= f.fields_for #agency.address do |address| %>
<%= render 'addresses/address_fields', f: address %>
<% end %>
#other fields which all work fine
<div id="branches">
<%= f.fields_for #agency.branches do |branch| %>
<%= render 'branch_fields', f: branch %>
<% end %>
I posted an earlier question about this and was advised to read lots of relevant info. I have read it and tried implementing about 30 different solutions. None of which have worked for me.
Here's what I've got.
I have a Miniatures model.
I have a Manufacturers model.
Miniatures have many manufacturers THROUGH a Productions model.
The associations seem to be set up correctly as I can show them in my views and create them via the console. Where I have a problem is in letting the Miniatures NEW and EDIT views create and update to the Productions table.
In the console the command #miniature.productions.create(manufacturer_id: 1) works, which leads me to believe I should be able to do the same in a form.
I THINK my problem is always in the Miniatures Controller and specifically the CREATE function. I have tried out a ton of other peoples solutions there and none have done the trick. It is also possible that my field_for stuff in my form is wrong but that seems less fiddly.
I've been stuck on this for days and while there are other things I could work on, if this association isn't possible then I'd need to rethink my entire application.
The form now creates a line in the Productions table but doesn't include the all important manufacturer_id.
Any help VERY much appreciated.
My New Miniature form
<% provide(:title, 'Add miniature') %>
<h1>Add a miniature</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#miniature) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :production do |production_fields| %>
<%= production_fields.label :manufacturer_id, "Manufacturer" %>
<%= production_fields.select :manufacturer_id, options_from_collection_for_select(Manufacturer.all, :id, :name) %>
<% end %>
<%= f.label :release_date %>
<%= f.date_select :release_date, :start_year => Date.current.year, :end_year => 1970, :include_blank => true %>
<%= f.submit "Add miniature", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
Miniatures controller
class MiniaturesController < ApplicationController
before_action :signed_in_user, only: [:new, :create, :edit, :update]
before_action :admin_user, only: :destroy
def productions
#production = #miniature.productions
end
def show
#miniature = Miniature.find(params[:id])
end
def new
#miniature = Miniature.new
end
def edit
#miniature = Miniature.find(params[:id])
end
def update
#miniature = Miniature.find(params[:id])
if #miniature.update_attributes(miniature_params)
flash[:success] = "Miniature updated"
redirect_to #miniature
else
render 'edit'
end
end
def index
#miniatures = Miniature.paginate(page: params[:page])
end
def create
#miniature = Miniature.new(miniature_params)
if #miniature.save
#production = #miniature.productions.create
redirect_to #miniature
else
render 'new'
end
end
def destroy
Miniature.find(params[:id]).destroy
flash[:success] = "Miniature destroyed."
redirect_to miniatures_url
end
private
def miniature_params
params.require(:miniature).permit(:name, :release_date, :material, :scale, :production, :production_attributes)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
end
Miniature model
class Miniature < ActiveRecord::Base
has_many :productions, dependent: :destroy
has_many :manufacturers, :through => :productions
accepts_nested_attributes_for :productions
validates :name, presence: true, length: { maximum: 50 }
validates :material, presence: true
validates :scale, presence: true
validates_date :release_date, :allow_blank => true
def name=(s)
super s.titleize
end
end
Production model
class Production < ActiveRecord::Base
belongs_to :miniature
belongs_to :manufacturer
end
Manufacturer model
class Manufacturer < ActiveRecord::Base
has_many :productions
has_many :miniatures, :through => :productions
validates :name, presence: true, length: { maximum: 50 }
accepts_nested_attributes_for :productions
end
Instead of calling:
#production = #miniature.productions.create
Try Rails' "build" method:
def new
#miniature = Miniature.new(miniature_params)
#miniature.productions.build
end
def create
#miniature = Miniature.new(miniature_params)
if #miniature.save
redirect_to #miniature
else
render 'new'
end
end
Using the build method uses ActiveRecord's Autosave Association functionality.
See http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
You also need to update your params method, e.g.
def miniature_params
params.require(:miniature).permit(:name, :release_date, :material, :scale, productions_attributes: [:manufacturer_id])
end
Also your fields_for should be plural (I think)...
<%= f.fields_for :productions do |production_fields| %>
So I have a Review model in my app for users leaving reviews for movies. I'm rendering a form for a new Review in the Show view for each individual movie. However, when I try to submit a blank review to check that the errors display on the page, they don't. What could I be doing wrong?
My review model:
class Review < ActiveRecord::Base
attr_accessible :content
belongs_to :user
belongs_to :movie
validates :user_id, presence: true
validates :movie_id, presence: true
validates :content, presence: true, length: { maximum: 1000 }
end
My reviews controller:
class ReviewsController < ApplicationController
before_filter :signed_in_user, only: [:new, :create, :destroy]
def new
end
def create
#I'm doing this to get around the mass assignment error.
movie_id = params[:review].delete(:movie_id)
#review = current_user.reviews.build(params[:review])
#review.movie_id = movie_id
if #review.save
flash[:success] = "Review created!"
redirect_to movie_path(#review.movie)
else
redirect_to movie_path(#review.movie)
end
end
def destroy
end
end
My form:
<%= form_for(:review, url: reviews_path) do |f| %>
<%= f.hidden_field :movie_id %>
<div class="field">
<%= f.text_area :content, placeholder: "Write a new review..." %>
</div>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
When you redirect away from the #create method, you're losing the errors... Change to
if #review.save
flash[:success] = "Review created!"
redirect_to movie_path(#review.movie)
else
render :new
end