I have the following set up in rails:
Document has_many Sections
Section belongs_to Document
The Section form is completed in the documents/show view...the Document controller for this action is:
def show
#document = current_user.documents.find(params[:id])
#section = Section.new if logged_in?
end
The Section form in documents/show is as follows:
<%= form_for(#section) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new section..." %>
</div>
<%= hidden_field_tag :document_id, #document.id %>
<%= f.submit "Post", class: "btn btn-primary" %>
<% end %>
Where you can see a hidden_field_tag is sending the document_id
The sections_controller is as follows:
class SectionsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy, :show, :index]
def create
#document = Document.find(params[:document_id])
#section = #document.build(section_params)
if #section.save
flash[:success] = "Section created!"
redirect_to user_path(current_user)
else
render 'static_pages/home'
end
end
def destroy
end
def index
end
private
def section_params
params.require(:section).permit(:content)
end
end
I get the following error which I have not been able to resolve.
**NoMethodError (undefined method `build' for #<Document:0x00000004e48640>):
app/controllers/sections_controller.rb:6:in `create'**
I am sure it must be something simple I am overlooking but can't seem to find it. Any help would be appreciated:
Replace the below line :-
#section = #document.build(section_params)
with
#section = #document.sections.build(section_params)
You have a has_many associations named sections in the Document model. Thus as per the guide, you got the method collection.build(attributes = {}, ...). Read the section 4.3.1.14 collection.build(attributes = {}, ...) under the link I gave to you.
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
In my views I have a form and trying to update quantity for an order line:
<div class="quantity">Quantity</br>
<%= form_tag(order_line_path(line.id), method: "patch") do %>
<%= number_field_tag 'qty', '1', within: 1...line.book.stock %>
<%= submit_tag "Update", class: "btn btn-primary" %>
<% end %>
</div>
The instance variable in the rest of my view is a collection of order lines, so I cannot use it.
Then I have in my controller the update method:
def update
#order = current_order
#order_line = #order.order_lines.find(params[:id])
#order_line.update_attributes(order_line_params)
end
And my strong params definition:
def order_line_params
params.require(:order_line).permit(:qty)
end
I get this error :
param is missing or the value is empty: order_line
Could someone please have a look?
Thanks!
The reason you are getting param is missing or the value is empty: order_line is that you are using form_tag which gives a "flat" params hash.
However this is easily avoidable if you just use form_with/form_for.
# routes.rb
resources :orders do
resources :line_items, shallow: true
end
# app/views/order_items/edit.html.erb
# use `form_with(model: #order_item)` in Rails 5
<%= form_for(#order_item) do |f| %>
<%= f.label :qty, within: 1...f.object.book.stock %>
<%= f.number_field :qty, %>
<%= f.submit %>
<% end %>
class OrderItemsController < ApplicationController
before_action :set_order_item, only: [:show, :edit, :update, :destroy]
# ...
# GET /order_items/:id/edit
def edit
end
# PATCH /order_items/:id
def update
if #order_item.update(order_item_params)
redirect_to #order_item, notice: 'Line updated'
else
render :edit
end
end
private
def set_order_item
#order_item = OrderItem.find(params[:id])
end
def order_item_params
params.require(:order_item).permit(:qty)
end
end
But what you're really looking for unless you are doing the update/creation of nested items with AJAX is most likely a combination of accepts_nested_attributes and fields_for which will let the user mass edit the line items:
class Order < ApplicationRecord
accepts_nested_attributes_for :order_items
end
<%= form_for(#order) do |f| %>
<%= fields_for(:order_items) do |oif| %>
<%= f.label :qty, within: 1...f.object.book.stock %>
<%= f.number_field :qty, %>
<% end %>
<%= f.submit %>
<% end %>
class OrdersController < ApplicationController
# PATCH /orders/:id
def update
if #order.update(order_params)
redirect_to #order, notice: 'Order updated.'
else
render :new
end
end
private
def order_params
params.require(:order).permit(order_items_attributes: [:qty])
end
end
I am following Michael Hartl's Rails Tutorial and have completed the part about creating microposts. I was wondering if anyone have an idea about how to make the micropost form responsive to a hyperlink. For example, when a user types in "Visit our HTML tutorial" in the micropost, I want the link to active. Any help would be appreciated.
micropost_controller.rb
class MicropostsController < ApplicationController
before_action :signed_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
def destroy
#micropost.destroy
redirect_to root_url
end
private
def micropost_params
params.require(:micropost).permit(:html)
end
def correct_user
#micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if #micropost.nil?
end
end
micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
default_scope -> { order('created_at DESC') } validates :content,
presence: true, length: { maximum: 140 } validates :user_id,
presence: true end
...
end
micropost_form.html.erb
<%= form_for(#micropost) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
You can use the sanitize helper method and pass in the anchor (a) tag as the only allowable tag. You don't use it when they create the post, you use it when you are showing the micropost in the view
app/views/microposts/show.html.erb
<%= sanitize micropost.content, tags: ['a'] %>
(I don't know exactly how you are showing the content of a micropost, but this should give you an idea)
This is safer than other options like html_safe because you can actually control which html tags you will allow the user to be able to input.
I'm using rails 4.0.8. I added a comment section to a model called 'Things', but I keep getting the same error "param is missing or the value is empty: thing" when I press the submit comment button. It says the error is in the Things#Controller. What am I doing wrong?
UPDATE: I removed the url path from the form, but a new error returns "Couldn't find Thing without an ID". The error is in Comments#Controller.
VIEW FOR THING/SHOW
<div id= "thing">
<h1>
<%= #thing.name %>
</h1>
<br>
<div id= "commentsection">
Comments
<div id= "comments">
<br>
<% #thing.comments.each do |c| %>
<%= c.username %>
<br>
<%= c.text %>
<% end %>
<%= form_for #comment, :url => thing_path do |f| %>
<%= f.label :username %>
<%= f.text_field :username %>
<%= f.label :comment %>
<%= f.text_field :text %>
<%= f.submit "Enter", class: "btn btn-small btn-primary" %>
<% end %>
</div>
</div>
</div>
THINGS CONTROLLER
class ThingsController < ApplicationController
def show
#thing = Thing.find(params[:id])
#thing.comments.build
#comment = Comment.new
end
def index
end
def new
#thing = Thing.new
#things = Thing.all
end
def create
#thing = Thing.new(thing_params)
if #thing.save
redirect_to #thing
else
render 'new'
end
end
private
def thing_params
params.require(:thing).permit(:name, :avatar)
end
end
COMMENTS CONTROLLER (I put asterisks around the line where the error is)
class CommentsController < ApplicationController
def show
#comment = Comment.find(params[:id])
end
def new
#comment = Comment.new
#comments = Comment.all
end
def create
****#thing = Thing.find(params[:thing_id])****
#comment = #thing.comments.create(comment_params)
redirect_to thing_path(#thing)
end
end
private
def comment_params
params.require(:comment).permit(:user, :text, :upvotes, :downvotes, :thing_id)
end
end
ROUTES
Website::Application.routes.draw do
get "comments/new"
get "comments/show"
get "things/new"
root 'home_page#home'
get "all/things/new" => 'things#new'
get "all/allthings"
resources :things
resources :good_comments
get "things/show"
get "things/results"
end
You are posting the #comment form to post '/things' path.
<%= form_for #comment, :url => thing_path do |f| %>
It should just be <%= form_for #comment do %> (Rails is smart enough to plug in the comments_path) or if you feel like being more explicit (even though it's not necessary)
<%= form_for #comment, url: :comments_path do %>
Another note though, if you want that Comment to be tied to that specific Thing then in your models it should be
Class Thing
has_many :comments
end
Class Comment
belongs_to :thing
end
Then make sure in your database comment has a thing_id foreign_key field and then your form for comment should actually look like
<%= form_for #thing, #comment do %>
<% end %>
Recieve this error in my Game page in Rails 4
ActionView::Template::Error (First argument in form cannot contain nil or be empty):
_Gamepost_form.html.erb
<%= form_for (#gamepost) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new game sale..." %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
Controller
class GamepostsController < ApplicationController
before_action :signed_in_user, only: [:create, :destroy, :]
before_action :correct_user, only: :destroy
def index
#gamepost = current_user.gameposts.build
end
def create
#gamepost = current_user.gameposts.build(gamepost_params)
if #gamepost.save
flash[:success] = "Game Sale created!"
redirect_to root_url
else
#gamefeed_items = []
render 'static_pages/home'
end
end
def destroy
#gamepost.destroy
redirect_to root_url
end
private
def gamepost_params
params.require(:gamepost).permit(:content)
end
def correct_user
#gamepost = current_user.gameposts.find_by(id: params[:id])
rescue
redirect_to root_url
end
end
Have a similar example using microposts and its working fine no idea why this instance is not working
After the answer Vee provided,
ActionView::Template::Error (undefined method `any?' for nil:NilClass):
1: <% if #gamefeed_items.any? %>
2: <ol class="gameposts">
3: <%= render partial: 'shared/gamefeed_item', collection: #gamefeed_items %>
This shows up in same page
Following up in the comments in your question.
Looks like you are rendering that form from your index action.
class GamepostsController < ApplicationController
before_action :signed_in_user, only: [ :index, :create, :destroy]
...
def index
#gamepost = current_user.gameposts.build
end
...
end
Note that, you also need to add :index and not :new in before_action :signed_in_user. This is to ensure that the current_user becomes available in so that current_user.gameposts.build does not fail!