For several hours trying to research a solution, found some very similar issues like this one, or this one, though all the proposed fixes don't solve my problem:
Attempting to build a nested form inside of a nested form with the Cocoon Gem, though the 3rd level child doesn't save to the database.
Pretty simple structure of the models, only "has_many / belongs_to" relationships:
A Text has many quotes. A Quote has many comments.
The dynamic UI interaction in the implementation works, adding and removing of fields work, unfortunately though only texts and quotes get saved, not the comments. No errors shown.
Here are the forms:
_text_form.html.erb
<%= form_for(@text, html: { multipart: true }) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_field :title, placeholder: "Enter Title" %>
</div>
<div>
<h3>Quotes:</h3>
<div id="quotes">
<%= f.fields_for :quotes do |quote| %>
<%= render 'quote_fields', f: quote %>
<% end %>
<div class="links">
<%= link_to_add_association 'add quote', f, :quotes, class: "btn btn-default" %>
</div>
</div>
</div>
<%= f.submit %>
<% end %>
_quote_fields.html.erb
<div class="nested-fields">
<%= f.text_area :content, placeholder: "Compose new quote..." %>
<span class="picture">
<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
</span>
<div>
<h3>Comments:</h3>
<div id="comments">
<%= f.fields_for :comments do |comment| %>
<%= render 'comment_fields', f: comment %>
<% end %>
<div class="links">
<%= link_to_add_association 'add comment', f, :comments, class: "btn btn-default" %>
</div>
</div>
</div>
<div class="links">
<%= link_to_remove_association "remove quote", f, class: "btn btn-default" %>
</div>
</div>
<script type="text/javascript">
$('#quote_picture').bind('change', function() {
var size_in_megabytes = this.files[0].size/1024/1024;
if (size_in_megabytes > 2) {
alert('Maximum file size is 2MB. Please choose a smaller file.');
}
});
</script>
_comment_fields.html.erb
<div class="nested-fields">
<%= f.text_area :bodycomment, placeholder: "Write a comment..." %>
<%= link_to_remove_association "remove comment", f, class: "btn btn-default" %>
</div>
Here are the models:
text.rb
class Text < ApplicationRecord
belongs_to :user, inverse_of: :texts
has_many :quotes, dependent: :destroy, inverse_of: :text
has_many :comments, :through => :quotes
accepts_nested_attributes_for :quotes, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :comments, reject_if: :all_blank, allow_destroy: true
default_scope -> { order(created_at: :desc) }
mount_uploader :coverimage, CoverimageUploader
validates :user_id, presence: true
validates :title, presence: true
validate :coverimage_size
private
# Validates the size of an uploaded picture.
def coverimage_size
if coverimage.size > 5.megabytes
errors.add(:coverimage, "should be less than 5MB")
end
end
end
quote.rb
class Quote < ApplicationRecord
belongs_to :text, inverse_of: :quotes
has_many :comments, dependent: :destroy, inverse_of: :quote
accepts_nested_attributes_for :comments, reject_if: :all_blank, allow_destroy: true
mount_uploader :picture, PictureUploader
validates :content, presence: true, length: { maximum: 350 }
validate :picture_size
private
#Validates size of image upload
def picture_size
if picture.size > 2.megabytes
errors.add(:picture, "should be less than 2MB")
end
end
end
comment.rb
class Comment < ApplicationRecord
belongs_to :quote, inverse_of: :comments
validates :quote_id, presence: true
validates :bodycomment, presence: true
end
And here the controllers:
quotes_controller.rb
class QuotesController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def show
end
def create
@quote = current_user.quotes.build(quote_params)
if @quote.save
flash[:success] = "Quote created!"
redirect_to root_url
else
@feed_items = []
render 'static_pages/home'
end
end
def destroy
@quote.destroy
flash[:success] = "Quote deleted"
redirect_to request.referrer || root_url
end
private
def quote_params
params.require(:quote).permit(:content, :picture, comments_attributes: [:id, :bodycomment
, :_destroy])
end
def correct_user
@quote = current_user.quotes.find_by(id: params[:id])
redirect_to root_url if @quote.nil?
end
end
comments_controller.rb
class CommentsController < ApplicationController
before_action :logged_in_user, only: [:create, :edit, :update, :destroy]
before_action :correct_user, only: :destroy
def show
end
def create
@comment = current_user.comments.build(comment_params)
if @comment.save
flash[:success] = "Comment created!"
redirect_to root_url
else
@feed_items = []
render 'static_pages/home'
end
end
def destroy
@comment.destroy
flash[:success] = "Comment deleted"
redirect_to request.referrer || root_url
end
private
def comment_params
params.require(:comment).permit(:bodycomment)
end
def correct_user
@comment = current_user.comments.find_by(id: params[:id])
redirect_to root_url if @comment.nil?
end
end
Wondering if it is some javascript issue...
Thanks so much for looking into this. Really appreciated, and thanks for helping out.
EDIT
Here is the texts_controller.rb
class TextsController < ApplicationController
before_action :logged_in_user, only: [:create, :edit, :update, :destroy]
before_action :correct_user, only: :destroy
before_action :find_text, only: [:show, :edit, :update, :destroy]
def show
end
def new
@text = current_user.texts.build
end
def create
@text = current_user.texts.build(text_params)
if @text.save
flash[:success] = "Text created!"
render 'show'
else
render 'static_pages/home'
end
end
def edit
end
def update
if @text.update(text_params)
redirect_to root_url
else
render 'edit'
end
end
def destroy
@text.destroy
flash[:success] = "Text deleted"
redirect_to request.referrer || root_url
end
private
def text_params
params.require(:text).permit(:url, :title, :coverimage,
:publication, :author, :summary, quotes_attributes: [:id, :content, :picture, :_destroy], comments_attributes: [:id, :bodycomment, :_destroy])
end
def find_text
@text = Text.find(params[:id])
end
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless current_user?(@user)
end
end
Here are some log infos that I get after saving the form field:
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
utf8: "✓"
authenticity_token: NQLh7TwlhfbV4ez91HGMyYZK6YYYiLXhHG/cAhrAsRylIAuFFhjnKX0vEO8ZIVbsxGES3byBgUMz21aSOlGiqw==
text: !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
title: Title of the Book
quotes_attributes: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
'1490626822148': !ruby/hash:ActiveSupport::HashWithIndifferentAccess
content: This is a quote from the book.
_destroy: 'false'
comments_attributes: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
'1490626833771': !ruby/hash:ActiveSupport::HashWithIndifferentAccess
bodycomment: Here is a comment on the quote of the book.
_destroy: 'false'
permitted: false
commit: Create Text
controller: texts
action: create
permitted: false
Here the log file form the terminal:
Started POST "/texts" for ::1 at 2017-03-27 17:00:51 +0200
Processing by TextsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"NQLh7TwlhfbV4ez91HGMyYZK6YYYiLXhHG/cAhrAsRylIAuFFhjnKX0vEO8ZIVbsxGES3byBgUMz21aSOlGiqw==", "text"=>{"title"=>"Title of the Book", "publication"=>"", "author"=>"", "url"=>"", "summary"=>"", "quotes_attributes"=>{"1490626822148"=>{"content"=>"This is a quote from the book.", "_destroy"=>"false", "comments_attributes"=>{"1490626833771"=>{"bodycomment"=>"Here is a comment on the quote of the book.", "_destroy"=>"false"}}}}}, "commit"=>"Create Text"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Unpermitted parameter: comments_attributes
(0.1ms) begin transaction
SQL (0.7ms) INSERT INTO "texts" ("user_id", "url", "title", "publication", "author", "summary", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?) [["user_id", 1], ["url", ""], ["title", "Title of the Book"], ["created_at", 2017-03-27 15:00:51 UTC], ["updated_at", 2017-03-27 15:00:51 UTC]]
SQL (0.4ms) INSERT INTO "quotes" ("content", "created_at", "updated_at", "text_id") VALUES (?, ?, ?, ?) [["content", "This is a quote from the book."], ["created_at", 2017-03-27 15:00:51 UTC], ["updated_at", 2017-03-27 15:00:51 UTC], ["text_id", 366]]
(1.3ms) commit transaction
Rendering texts/show.html.erb within layouts/application
Quote Load (0.2ms) SELECT "quotes".* FROM "quotes" WHERE "quotes"."text_id" = ? [["text_id", 366]]
Rendered texts/show.html.erb within layouts/application (5.8ms)
Rendered layouts/_shim.html.erb (0.5ms)
Rendered layouts/_header.html.erb (1.4ms)
Rendered layouts/_footer.html.erb (1.8ms)
Completed 200 OK in 127ms (Views: 100.1ms | ActiveRecord: 3.1ms)
text.rb
... I had to put in accepts_nested_attributes_for :comments
... in addition to the already posted oneTextController
... your nested permitted params needs to happen here, .permit(:content, :picture, quotes_attributes: [:id, :content, :picture, :_destroy, comments_attributes: [:id, :bodycomment, :_destroy]])
I might have the variable names off (especially the TextController which you didn't list) a bit, but basically, it looks like your are trying to inherit the nesting via the other controllers - when the only controller being called is the TextController.
To be sure, drop a tail -f log/<logname>
in a 2nd console or view the terminal console when you execute the save/update to watch for permit issues.
Let me know if there's still issues!
...update based on new controller
Controller needs to be adjusted (you have one misplaced ']' right now).
params.require(:text).permit(:url, :title, :coverimage,
:publication, :author, :summary, quotes_attributes: [:id, :content, :picture, :_destroy, comments_attributes: [:id, :bodycomment, :_destroy]])
That won't work till you fix the model too ...
accepts_nested_attributes_for :comments