Mailboxer N+1 Query detected - ruby-on-rails

I managed to install Mailboxer with this tutorial but i have a recurring error :
N+1 Query detected
Mailboxer::Message => [:sender]
Add to your finder: :includes => [:sender]
N+1 Query method call stack
app/views/conversations/_messages.html.erb:12:in `block in _app_views_conversations__messages_html_erb__3829465222244059655_69982701059040'
app/views/conversations/_messages.html.erb:1:in `_app_views_conversations__messages_html_erb__3829465222244059655_69982701059040'
app/views/conversations/show.html.erb:27:in `_app_views_conversations_show_html_erb__1439517360344897040_69982700516580'
app/views/conversations/_messages.html.erb:12:in `block in _app_views_conversations__messages_html_erb__3829465222244059655_69982701059040'
app/views/conversations/_messages.html.erb:1:in `_app_views_conversations__messages_html_erb__3829465222244059655_69982701059040'
app/views/conversations/show.html.erb:27:in `_app_views_conversations_show_html_erb__1439517360344897040_69982700516580'
I had another one for :message but i fixed the problem with includes. If i try to do the same with :sender, i have this error:
Association named 'sender' was not found on Mailboxer::Receipt; perhaps you misspelled it?
Tutorial original code : conversations_controller.rb
class ConversationsController < ApplicationController
before_action :authenticate_user!
def new
end
def create
recipients = User.where(id: conversation_params[:recipients])
conversation = current_user.send_message(recipients, conversation_params[:body], conversation_params[:subject]).conversation
flash[:success] = "Your message was successfully sent!"
redirect_to conversation_path(conversation)
end
def show
#receipts = conversation.receipts_for(current_user)
# mark conversation as read
conversation.mark_as_read(current_user)
end
def reply
current_user.reply_to_conversation(conversation, message_params[:body])
flash[:notice] = "Your reply message was successfully sent!"
redirect_to conversation_path(conversation)
end
def trash
conversation.move_to_trash(current_user)
redirect_to mailbox_inbox_path
end
def untrash
conversation.untrash(current_user)
redirect_to mailbox_inbox_path
end
private
def conversation_params
params.require(:conversation).permit(:subject, :body,recipients:[])
end
def message_params
params.require(:message).permit(:body, :subject)
end
end
Tutorial original code : show.html.erb
<div class="row">
<div class="spacer"></div>
<div class="col-md-6">
<%= link_to "Compose", new_conversation_path, class: "btn btn-success" %>
</div>
<div class="col-md-6 text-right">
<% if conversation.is_trashed?(current_user) %>
<%= link_to 'Untrash', untrash_conversation_path(conversation), class: 'btn btn-info', method: :post %>
<% else %>
<%= link_to 'Move to trash', trash_conversation_path(conversation), class: 'btn btn-danger', method: :post,
data: {confirm: 'Are you sure?'} %>
<% end %>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-body">
<%= render 'mailbox/folders' %>
</div>
</div>
</div>
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-body">
<%= render partial: 'messages' %>
</div>
<div class="panel-footer">
<!-- Reply Form -->
<%= form_for :message, url: reply_conversation_path(conversation) do |f| %>
<div class="form-group">
<%= f.text_area :body, placeholder: "Reply Message", rows: 4, class: "form-control" %>
</div>
<%= f.submit "Reply", class: 'btn btn-danger pull-right' %>
<% end %>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
Tutorial original code : _messages.html.erb
<% #receipts.each do |receipt| %>
<% message = receipt.message %>
<div class="media">
<div class="media-left">
<!-- user avators can go here -->
<a href="#">
<img class="media-object" src="http://placehold.it/64x64" alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">
<%= message.sender.username %> <br>
<small><b>Subject: </b><%= message.subject %></small><br>
<small><b>Date: </b><%= message.created_at.strftime("%A, %b %d, %Y at %I:%M%p") %></small>
</h4>
<%= message.body %>
</div>
</div>
<% end %>
If i delete <%= message.sender.username %> the problem is solved...
Any ideas ?

Normally, to fix these N+1 query problems we use an includes and by passing a hash, we can includes nested associations. So looking at this code, calling receipt.message.sender is triggering the error, so we have a Receipt model, a message association on it and a sender associated to that. So if we can find where we load the Receipt we can add includes(message: :user) like we would for any other model.
Digging into the mailboxer gem, the receipts_for method in your show action is just a wrapper for a couple of scopes on Mailboxer::Receipt. Since this method just runs some scopes for you, we can chain onto the end of it as if it were a normal ActiveRecord where chain.
So with all that in mind, we should be able to add on our includes and avoid the N+1 query problem, ending up with something like
#receipts = conversation.receipts_for(current_user).includes(message: :sender)

Related

Rails ActiveStorage unable to remove images on the localhost due to wrong path

I'm working with ActiveStorage and I'm able to upload images and display without issue but unable to purge or deleted one of the images.
Here is the controller code:
def photo_upload
#images = #tool.images
end
def delete_image_attachment
#images = ActiveStorage::Attachment.find(params[:id])
#images.purge
redirect_back(fallback_location: request.referer)
end
private
def set_tool
#tool = Tool.find(params[:id])
end
def is_authorised
redirect_to root_path, alert: "You don't have permission" unless current_user.id == #tool.user_id
end
def tool_params
params.require(:tool).permit(:tool_type, :power_type, :accessories, :brand, :listing_name, :summary, :address, :is_machine,
:is_hand, :is_cordless, :is_cord, :price, :active ,images: [])
end
end
...and the .erb files
<% if #tool.images.attached? %>
<div class="panel panel-default">
<div class="panel-heading">
Photo Display
</div>
<br>
<div class="row">
<% #tool.images.each do |image| %>
<div class="col-md-4">
<div class="panel panel-default tools-gallery-panel">
<div class="panel-heading preview ">
<%= link_to image_tag(image, class:"img-thumbnail tools-gallery"), image %>
</div>
<div class="panel-body">
<span class="pull-right">
<i class="fa fa-trash-o fa-lg" aria-hidden="true"></i>
<%= link_to "Remove", delete_image_attachment_tool_path(image), method: :delete, data: {confirm: "Are you sure?"} %>
</span>
</div>
</div>
</div>
<% end %>
<% end %>
</div>
I get an error as it's a wrong ID as it linked to tools_id to a user ID, I am bit lost, I hope you can pinpoint the error
Sound like you have a before_action :set_tool, add the option except: :delete_image_attachment
before_action :set_tool, except: :delete_image_attachment

form_with produces first record as nil

comment controller
class CommentsController < ApplicationController
before_action :load_commentable
before_action :checked_logged_in, only: [ :create]
def new
#comment = #commentabl.comments.new
end
def create
#comment = #commentable.comments.new(comment_params)
#comment.user_id = current_user.id
#comment.commenter = current_user.username
if #comment.blank? || #comment.save
flash[:success] = "Commented was created"
ActionCable.server.broadcast 'comment_channel',
commenter: current_user.username,
comment: #comment.content
redirect_to #commentable
else
flash[:danger] = render_to_string(:partial => 'shared/error_form_messaging',
:locals => {obj: #comment},
format: :html)
redirect_to #commentable
end
end
private
def comment_params
params.require(:comment).permit(:content, :commenter, :user_id)
end
def load_commentable
resource, id = request.path.split('/')[1,2]
#commentable = resource.singularize.classify.constantize.find(id)
end
def checked_logged_in
unless logged_in?
flash[:danger] = 'please log in to be able to comment'
redirect_to login_path
end
end
end
my form for creating a comment:
<%= form_with model:[commentable, commentable.comments.new], :html => {class: "form-horizontal", role:"form"} , local: true do |form| %>
<div class="form-group">
<div class="control-label col-sm-2">
<%= form.label :content, 'Comment' %>
</div>
<div class="col-sm-8">
<%= form.text_field :content , class: 'form-control', placeholder: "enter your comment here", autofocus: true %>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<%= form.submit 'Comment' , class: ' btn btn-primary' %>
</div>
</div>
<% end %>
the form is called in show.html.erb
<h2 class="text-center">
Title: <%= #article.title %>
</h2>
<div class="well col-xs-8 col-xs-offset-2">
<div id="user-info-showpage" align="center">
Created by: <%= render 'shared/user-info', obj: #article.user %>
</div>
<h4 class="text-center">
<strong>Description:</strong>
</h4>
<hr />
<%= simple_format(#article.description) %>
<% if #article.categories.any? %>
<p>Categories: <%= render #article.categories %></p>
<% end %>
<div class="article-actions">
<% if logged_in? && (current_user == #article.user || current_user.admin?) %>
<%= link_to "Delete", article_path(#article), method: :delete,
data: {confirm: "Are you sure you want to delete the article?"},
class: 'btn btn-xs btn-danger' %>
<%= link_to "Edit", edit_article_path(#article), class: 'btn btn-xs btn-success'%>
<%end%>
<%= link_to "View All Articles", articles_path , class: 'btn btn-xs btn-primary'%>
</div>
</div>
<% if logged_in? %>
<div class="col-xs-8 col-xs-offset-2">
<%#= render partial: 'comments/form', :locals => {commentable: #article} %>
</div>
<%end%>
<div class="col-xs-8 col-xs-offset-2">
<div id="comments"></div>
<%= #article.comments.inspect %>
<% #article.comments.each do |c| %>
<div class="well">
<%= c.content %> by
<%= c.commenter %>
</div>
<%end%>
<div id="comments"></div>
</div>
my result is in view is
Please if more info needed, ask me so I can provide
Note: I am not sure this empty record is owing to commentable.comments to be nil or I miss something
I commented render form in show page and now the empty record is gone, so my issue must be related to form_with
From my understanding, you
Expect:
in your articles#show page to not show the empty by _________ <div> HTML because the comment is still built (still in-memory), and not yet saved (not yet in DB).
Solution 1:
app/views/articles/show.html.erb
...
<div class="col-xs-8 col-xs-offset-2">
<div id="comments"></div>
<% #article.comments.each do |c| %>
<!-- ADD THIS LINE -->
<% if c.persisted? %>
<div class="well">
<%= c.content %> by
<%= c.commenter %>
</div>
<% end %>
<%end%>
<div id="comments"></div>
</div>
...
Solution 2 (better but still is a workaround):
app/views/comments/_form.html.erb
<%= form_with model:[commentable, Comment.new(commentable: commentable)], :html => {class: "form-horizontal", role:"form"} , local: true do |form| %>
Explanation:
The reason the page is displaying an empty by _________ <div> is that because you "built" a new comment before .each is called. Because they are sharing same memory space, the build basically also adds it to the array in-memory. See the following:
# rails console
article = Article.create!
comment1 = Comment.create!(commentable: article)
# from here, comment1 is then saved already in the DB
# now let's see what happens when you use "build" or "new"
# They have differences, it seem: for details: https://stackoverflow.com/questions/1253426/what-is-the-difference-between-build-and-new-on-rails/1253462
# redefine (to simulate your #article = Article.find(params[:id])
article = Article.find(article.id)
comment2 = article.comments.build
puts article.comments.count
# SQL: Select count(*) FROM ...
# => 1
puts article.comments.size
# => 2
# notice how `count` and `size` are different. `count` value is "DB-based" while `size` is "memory-based". This is because `count` is an `ActiveRecord` method while `size` is a delegated `Array` method.
# now let's simulate your actual problem in the view, where you were looping...
article.comments.each do |comment|
puts comment
end
# => <Comment id: 1>
# => <Comment id: nil>
# notice that you got 2 comments:
# one is that which is already persisted in DB
# and the other is the "built" one
# the behaviour above is to be expected because `.each` is a delegated `Array` method
# which is agnostic to where its items come from (DB or not)
This is the reason why in your page, the "built" comment is shown in the page because you are calling
<%= render partial: 'comments/form', :locals => {commentable: #article} %>
... which calls commentable.comments.build
BEFORE the <% "article.comments.each do |c| %>
If this is not clear enough yet, try putting
<%= render partial: 'comments/form', :locals => {commentable: #article} %>
... which calls commentable.comments.build
AFTER the <% "article.comments.each do |c| %> ... <% end %>
... and the by _________ <div> should already not show up.

Rails show error message of another model

I have a html partial that displays errors:
shared/model_errors.html.erb
<% model.errors.full_messages.each do |msg| %>
and I render it at top of each html file like
<%= render partial: 'shared/model_errors',locals: {model: #model_to_check_errors}%>
This is working fine for most of them.
Let's say that I'm on the show.html.erb template of the ConversationsController, and I have there a form_tag which submits messages to the MessagesController. If the submitted message is empty, the MessagesController won't allow it to be saved, because it has validation errors. How do I display those validations errors in my show.html.erb, considering I don't have access to the #message object of the create action of the MessagesController. Is there any way to add the error message to the ConversationsController? They will be displayed that way.
Edit:
show.html.erb of the ConversationsController
<div class="container nav_exp">
<%= render partial: 'shared/model_errors',locals: {model: #???}%>
<div class="ch-body">
<div class="col-md-12 bg-white ">
<div class="chat-message">
<ul class="chat" id="messages">
<% if #messages != nil %>
<%= render partial: 'conv_message', collection: #messages,:as => :message%>
<% else %>
<div class="panel-body hidden"></div>
<%end%>
</ul>
</div>
<%=form_tag(messages_path, :method=>'post',authenticity_token: true,remote: true) do%>
<div class="chat-box bg-white">
<div class="input-group">
<%= text_field_tag :body,"", id:"message_body",class: "form-control border no-shadow no-rounded", placeholder: "Type your message here"%>
<%= hidden_field_tag :conversation_id, #conversation.id %>
<%= hidden_field_tag :receiver, #other.id %>
<span class="input-group-btn">
<%= submit_tag 'Submit', class: "btn btn-success no-rounded" %>
</span>
</div><!-- /input-group -->
</div>
<% end %>
</div>
</div>
</div>
The create method of the MessagesController - messages_path
def create
message = Message.new(message_params)
message.user = current_user
if message.save
...
else
puts 'not working'
'I think 1 solution would be to add here flash messsages'
end
end
private
def message_params
params.permit(:body, :conversation_id, :receiver)
end

Error: param is missing or the value is empty:retweet

I am new to Rails. I want to pass parameters to DB via controller, but I receive this error param is missing or the value is empty:retweet. I think the problem is the way of passing parameters in view.
Here is the view
<% for #p in #posts %>
<div class="panel panel-default post-panel">
<div class="panel-body row">
<div class="col-sm-1">
<img src="/avatar.png" class="rounded-img" height="50px" width="50px">
</div>
<div class="col-sm-11">
<p class="post-title"><%= User.find(#p.user_id).username %> <span class="post-creation">- <%= #p.created_at.to_formatted_s(:short) %></span></p>
<p class="post-content"><%= #p.content %></p>
</div>
<div class="col-sm-12">
<p class="post-links">
<span class="glyphicon glyphicon-comment g-links" aria-hidden="true"></span>
**<%= form_for Retweet.new do |f| %>
<%= hidden_field_tag current_user.id, (:user_id) %>
<%= hidden_field_tag #p.id, (:post_id) %>
<%= f.submit "Retweet", class:"btn btn-primary"%>**
<% end %>
And here is the Controller
def new
#retweet = Retweet.new
end
def create
#retweet = Retweet.new(post_params)
redirect_to(:back)
end
private def post_params
params.require(:retweet).permit(:user_id, :post_id)
end
end
You should read up some tutorials on basic REST controllers in Rails. Your create action didn't save the retweet.
def create
#retweet = Retweet.new(post_params)
if #retweet.save
redirect_to(:back)
else
render action: 'new'
end
end
Edit: Just noticed your form is all wrong:
<%= form_for Retweet.new do |f| %>
<%= f.hidden_field :user_id, current_user.id %>
<%= f.hidden_field :post_id, #p.id %>
<%= f.submit "Retweet", class:"btn btn-primary"%>**
<% end %>
Mind you that you shouldn't allow settign the user_id like this, it is very easy to change it and thus mess around with your data. Instead you should add this to retweet#create:
#retweet.user = current_user

Implement Carrierwave_direct in Wicked Wizard Rails 4.2

I have an app set up with a Wicked Wizard form to create a new post.
The last step allows users to upload pictures to their post.
For image processing I am using Carrierwave and Fog (because I'm hosting the site on heroku and I am using Amazon S3 for storage).
It's all working fine, but the upload to Amazon S3 is very slow.
Following Ryan Bates' Uploading to Amazon S3 railscast (http://railscasts.com/episodes/383-uploading-to-amazon-s3) I tried Carrierwave_Direct (https://github.com/dwilkie/carrierwave_direct) to speed up the painfully slow upload to Amazon S3.
When doing this, however, I get an error:
NoMethodError in PostSteps#show
Showing
/Users/petersonneveld/rails_projects/doamer/doamer2/app/views/property_steps/picture.html.erb where line #1 raised:
undefined method `direct_fog_url' for nil:NilClass
I really don't know what to do to solve this. Does anyone have advice? Thanks a bunch for your help!
In the last step of my wicked wizard form I have
# app/views/post_steps/picture.html.erb
<%= direct_upload_form_for #uploader, url: wizard_path do |f| %>
<div class="panel panel-default">
<div class="panel-heading">
<div class="panel-title">
<h2>Let's post some pictures!</h2>
</div>
</div>
<div class="panel-body">
<% if #post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% #post.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group col-md-12">
<%= f.label :image, "Upload image" %><br>
<%= image_tag(#post.image_url(:thumb)).to_s if #post.image? %>
<%= f.file_field :image %>
</div>
<div class="form-group col-md-12">
<%= f.label :remote_image_url, "or paste image URL" %>
<%= f.text_field :remote_image_url, class: "form-control" %>
<%= f.hidden_field :image_cache %>
</div>
<div class="form-group col-md-12">
<h4><%= f.check_box :remove_image %>
<span class="label label-danger">Remove image</span></h4>
</div>
<div class="form-group col-md-12">
<%= f.button "Finish", class: "btn btn-primary", data: {disable_with: "<i class='fa fa-spinner fa-spin'></i> Saving post..."} %>
</div>
</div>
</div>
<% end %>
And in my controller
# app/controllers/post_steps_controller.rb
class PostStepsController < ApplicationController
include Wicked::Wizard
steps :description, :picture
def show
#post = Post.find(params[:post_id])
render_wizard
end
def update
#post = Post.find(params[:post_id])
#post.update_attributes(post_params)
render_wizard #post
end
private
def post_params
params.require(:post).permit(...)
end
def redirect_to_finish_wizard(options = nil)
redirect_to #post, notice: "Thanks for submitting a post."
end
end
And my post controller create action
# app/controllers/post_controller.rb
def create
#post = current_user.posts.build(post_params)
if #post.save
redirect_to post_steps_path(:id => "description", :post_id => #post.id)
else
render :new
end
end
Found the answer: had to update the 'aws-sdk' gem!

Resources