I am fairly new to Rails 4, and I am creating a Q&A site similar to Stack Overflow.
I have a question page much like the one on Stack Overflow. The question part is working, and then I have answers, which are working too, HOWEVER, I want to be able to have the answerer's name next to their answer.
I am pretty sure I have the controller, model and view set up properly. I have reset the database but it still comes up with errors.
Here's my code:
#Answers_controller.rb create action
def create
#answer = #question.answers.new(answer_params)
#answer.user_id = current_user.username
respond_to do |format|
if #answer.save
format.html { redirect_to [#question], notice: 'Answer was successfully created.' }
format.json { render action: 'show', status: :created, location: #answer }
else
format.html { render action: 'new' }
format.json { render json: #answer.errors, status: :unprocessable_entity }
end
end
end
answer.rb (model):
class Answer < ActiveRecord::Base
belongs_to :user
belongs_to :question
validates :body, presence: true
end
#Then in user.rb I have has_many :answers, has_many :questions
#Also in question.rb has_many :answers
answers/_answer.html.erb:
<%= answer.username %>
<%= answer.body %>
</p>
<p>
<% if current_user.present? && current_user == answer.user %>
<%= link_to 'Delete Answer', [answer.question, answer],
method: :delete,
data: { confirm: 'Are you sure?' } %>
<% end %>
</p>
Then in questions/show:
<div class="container">
<div class="row">
<div class="span8">
<h3><%= #question.title %>
<% if current_user.present? && current_user == #question.user %>
<%= link_to 'Edit', edit_question_path(#question) %></h3>
<% end %>
<hr>
</div>
</div>
<div class="row">
<div class="span8">
<p><%= #question.description %></p>
Asked by <%= #question.user.full_name %>
<hr>
</div>
</div>
<%= render "answers/form" %>
<%= render #question.answers %>
</div>
<script type="text/javascript">
$(document).ready(function(){
$('.wysihtml5').each(function(i, elem) {
$(elem).wysihtml5();
});
})
</script>
It looks like you wrong here
#answer = #question.answers.new(answer_params)
try
#answer = #question.answers.build(answer_params)
Explanaition: you build a new answer but do not refer it to question because according to has_many documentation it has collection.build(attributes = {}, …) not collection.new(attributes).
A couple of things that stand out:
#Answers_controller.rb create action
def create
#answer = #question.answers.new(answer_params)
#answer.user_id = current_user.username # ???? should be current_user.id ????
# omitted
end
answers/_answer.html.erb
<%= answer.username %> # ???? you have no username field on your answer model ???
# ??? should be <%= answer.user.username %> ???
<%= answer.body %>
</p>
# omitted
Related
I have a Post model and Comment, and Post has_many Comment and I wanna real-timely update the comment when you are in a specific post, meaning, the comments live-updating should only visible when multiple users are in the same post, for example: in posts/6
For View I have:
<div>
<p>
<strong>Title:</strong>
<%= post.title %>
</p>
<p>
<strong>Content:</strong>
<%= post.content %>
</p>
<p>
<strong>User:</strong>
<%= post.user.username %>
</p>
</div>
<hr/>
<%= form_for [post, Comment.new] do |f| %>
Body: <%= f.text_area :body, rows: 5, cols: 20 %>
<%= f.submit "Submit" %>
<% end %>
<hr/>
<h1>Comments:</h1>
<%= turbo_stream_from post %>
<%= turbo_frame_tag post do %>
<div id="<%= dom_id(post) %>">
<% post.comments.includes(:user).each do |comment| %>
<%= render comment %>
<% end %>
</div>
<% end %>
In Comment#create action I have:
post = Post.find(params[:post_id])
#comment = post.comments.new(comment_params)
#comment.user_id = current_user.id
respond_to do |format|
if #comment.save
format.turbo_stream { render turbo_stream: turbo_stream.prepend("post_#{post.id}", partial: "comments/comment", locals: { comment: #comment }) }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
In Comment model I have:
after_create_commit {
broadcast_prepend_to(target: "post_#{self.post.id}")
}
And in terminal I can see
[ActionCable] Broadcasting to : "<turbo-stream action=\"prepend\" target=\"post_6\"><template> <div>\n <p>\n <strong>Title:</strong>\n adsd\n </p>\n\n <p>\n <strong>Content:</strong>\n Asda\n </p>\n\n <p>\n <strong>User:</strong>\n Super Saiyan Vegeta\n </p>\n </div>\n\n <hr/>\...
Which is correct, but the comments list in a specific post still not updating, and I do see I have id="post_6" in the view...can someone tell me why? Thanks.
Try to change
<div id="<%= dom_id(post) %>">
to
<div id="post_<%= post.id %>">
And use such method in controller
if #comment.save
#comment.broadcast_prepend_to to post, target: "post_#{post.id}", partial: "comments/comment", locals: { comment: #comment }
end
instead of broadcast from model
I followed a tutorial to build a Restaurant reviews site on Ruby on Rails and i adapted it for Hospitals.
I would like to display results from the database in a sorted format like highest reviews or highest number of reviews or by location (all available data in the database) in a gridview (limited to Top 6).
I am unsure as to where to even start , I know it might sound a but simple but i have researched and keep seeing so many options and got confused as to where to start.
Some solutions seem to fit small data sets but i fully expect this to grow exponentially in the future so the most effective way would be best for me.
I have SQLlite on my local environment and Postgres in my production environment.
This is what i have on my search page that displays all my Hospitals and what i have in my index page which displays all records in a grid. Any assistance would be greatly appreciated.
search.html.erb
<div class="table-responsive table-box card">
<%= form_tag search_hospitals_path, method: :get, class: "form-group" do %>
<div class="input-group">
<div class="input-group-addon">Search and Review</div>
<p>
<%= text_field_tag :search, params[:search], class: "form-control formInput", placeholder: "Hospital Name" %>
</p>
</div>
<% end %>
<div class="container hospital_display">
<% #hospitals.each do |hospital| %>
<div class="row">
<div class="col-sm-6">
<%= link_to image_tag(hospital.image), hospital, class: "responsive" %>
</div>
<div class="col-sm-6">
<%= link_to hospital.name, hospital %><br>
<%= hospital.address %><br>
<%= hospital.phone %><br>
<%= link_to hospital.website, hospital.website %>
<%# Checks for admin %>
<% if user_signed_in? && current_user.admin? %>
<%= link_to 'Edit', edit_hospital_path(hospital), class: "btn btn-link" %>
<%= link_to 'Destroy', hospital, method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-link" %>
<% end%>
<%# Rating %>
<% if hospital.reviews.count == 0 %>
No reviews yet, be the first to write one
<% elsif hospital.reviews.count == 1 %>
<div class="star-rating" data-score= <%= hospital.avg_rating %> ></div>
1 Review
<% else %>
<div class="star-rating" data-score= <%= hospital.avg_rating %> ></div>
<%= hospital.length %> <%= "Reviews" %>
<% end %>
</div>
</div>
<% end %>
</div>
</div>
</div>
</div>
<br>
<script>
$('.star-rating').raty({
path: 'https://s3-us-west-2.amazonaws.com/morafamedapp/stars',
readOnly: true,
score: function() {
return $(this).attr('data-score');
}
});
</script>
I have this on my index page
index.html.erb
<div class="jumboFluid">
<div class="jumbotron">
<section class="content">
<%= form_tag search_hospitals_path, method: :get, class: "form-group" do %>
<div class="input-group">
<div class="input-group-addon">Search</div>
<p>
<%= text_field_tag :search, params[:search], class: "form-control formInput", placeholder: "Eye, Maternity" %>
<%# <%= submit_tag "Search", name: nil, class: "btn btn-default" %>
</p>
</div>
<% end %><br>
<h3 class="intro2">Find the best HealthCare Options around you with Medapp.
<br><%# Explore the best of healthcare available in your community.<br>
Read and leave reviews to assist others seeking the best health care and keep our hospitals on their toes. %></h3>
</section>
</div>
</div>
<div class="hospitalList">
<h1 id="hospitalBanner">Hospitals</h1>
<blockquote>
<p class="text-center"><cite>“Explore the best of healthcare available in your community”</cite> </p>
</blockquote>
<% content_for(:body_attributes) do %>
data-no-turbolink="false"
<% end %>
<main>
<div class="container-fluid fluid">
<div class="row">
<% #hospitals.each do |hospital| %>
<div class="col-md-4 col-xs-6">
<div class="thumbnail">
<%= link_to image_tag(hospital.image), hospital %>
<div class="caption">
<h4> <%= link_to hospital.name, hospital %></h4><br>
<% if hospital.reviews.count == 0 %>
0 Reviews
<% elsif hospital.reviews.count == 1 %>
<div class="star-rating" data-score= <%= hospital.avg_rating %> ></div>
1 Review
<% else %>
<div class="star-rating" data-score= <%= hospital.avg_rating %> ></div>
<%= hospital.length %> <%= "Reviews" %>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</main>
<br>
<% if user_signed_in? && current_user.admin? %>
<%= link_to 'New Hospital', new_hospital_path, class: "btn btn-primary btn-lg btn-special" %>
<% end %>
<!--Start of Tawk.to Script-->
<script type="text/javascript">
window.$_Tawk = undefined;
var Tawk_API=Tawk_API||{}, Tawk_LoadStart=new Date();
(function(){
var s1=document.createElement("script"),s0=document.getElementsByTagName("script")[0];
s1.async=true;
s1.src='https://embed.tawk.to/592da976b3d02e11ecc677a1/default';
s1.charset='UTF-8';
s1.setAttribute('crossorigin','*');
s0.parentNode.insertBefore(s1,s0);
s1.style.background = 'yellow';
})();
</script>
<!--End of Tawk.to Script-->
<script>
$('.star-rating').raty({
path: 'https://s3-us-west-2.amazonaws.com/morafamedapp/stars',
readOnly: true,
score: function() {
return $(this).attr('data-score');
}
});
</script>
</div>
Hospitals_controller.rb
class HospitalsController < ApplicationController
before_action :set_hospital, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:search, :index, :show]
before_action :check_user, except: [:search, :index, :show]
protect_from_forgery with: :null_session
protect_from_forgery except: ["create"]
def search
if params[:search].present?
#hospitals = Hospital.search(params[:search])
else
#hospitals = Hospital.all
end
end
def import
Hospital.import(params[:file])
end
# GET /hospitals
# GET /hospitals.json
def index
#hospitals = Hospital.all
end
# GET /hospitals/1
# GET /hospitals/1.json
def show
#reviews = Review.where(hospital_id: #hospital.id).order("created_at DESC")
if #reviews.blank?
#avg_rating = 0
else
#avg_rating = #reviews.average(:rating).round(2)
end
end
# GET /hospitals/new
def new
#hospital = Hospital.new
end
# GET /hospitals/1/edit
def edit
end
# POST /hospitals
# POST /hospitals.json
def create
import if params[:file] # <= this here is the call to your import method
#hospital = Hospital.new(hospital_params)
respond_to do |format|
if #hospital.save
format.html { redirect_to #hospital, notice: 'Hospital was successfully created.' }
format.json { render :show, status: :created, location: #hospital }
else
format.html { render :new }
format.json { render json: #hospital.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /hospitals/1
# PATCH/PUT /hospitals/1.json
def update
respond_to do |format|
if #hospital.update(hospital_params)
format.html { redirect_to #hospital, notice: 'Hospital was successfully updated.' }
format.json { render :show, status: :ok, location: #hospital }
else
format.html { render :edit }
format.json { render json: #hospital.errors, status: :unprocessable_entity }
end
end
end
# DELETE /hospitals/1
# DELETE /hospitals/1.json
def destroy
#hospital.destroy
respond_to do |format|
format.html { redirect_to hospitals_url, notice: 'Hospital was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_hospital
#hospital = Hospital.find(params[:id])
end
def check_user
unless current_user.admin?
redirect_to root_url, alert: "Sorry, only admins can do that!"
end
end
# Never trust parameters from the scary internet, only allow the white list through.
def hospital_params
params.require(:hospital).permit(:name, :address, :city_town, :state, :phone, :website, :safe_care, :jci, :cohsasa, :best_known_4, :image )
end
end
In your Hospital model, you could just define scopes that you can append to any query:
# app/models/hospital.rb
class Hospital < ActiveModel
scope :order_by_highest_rated, -> { includes(:reviews).group("hospital_id").order("avg(reviews.rating) desc") }
scope :order_by_most_reviews, -> { includes(:reviews).group("hospital_id").order("count(reviews.id) desc") }
end
then in your Hospital controller, you can add functionality to choose the sort order
class HospitalsController < ApplicationController
def index
if params[:search_by] == 'highest_ranked'
#hospitals = Hospital.order_by_highest_rated
else
#hospitals = Hospital.order_by_most_reviews
end
end
end
See here and here for more readings
If you don't care about accessing the value itself and only want the order I suggest going with andrew21s answer. With that out of the way, let's get to my answer.
The question is simple enough although the answer is more complicated than you would expect. I assume the following associations in the models:
class Hospital < ApplicationRecord
has_many :reviews
end
class Review < ApplicationRecord
belongs_to :hospital
end
With the above present this issue can be solved with the following scopes:
class Hospital < ApplicationRecord
# ...
def self.include_review_count
review_count = Review.select(Review.arel_table[:id].count)
.where(Review.arel_table[:hospital_id].eq(arel_table[:id]))
.as('review_count')
select(*attribute_names.map(&:to_sym)).select(review_count)
end
def self.include_review_average
review_average = Review.select(Review.arel_table[:rating].average)
.where(Review.arel_table[:hospital_id].eq(arel_table[:id]))
.as('review_average')
select(*attribute_names.map(&:to_sym)).select(review_average)
end
# ...
end
With these scopes present you can now do the following in your controller:
class HospitalsController < ApplicationController
# ...
def index
#hospitals = Hospital.include_review_count
.include_review_average
.order('review_count DESC', 'review_average DESC', :id)
.limit(6)
end
# ...
end
Note: In the code above you can't use .order(review_count: :desc, review_average: :desc, id: :asc) because "review_count" is not a real attribute, but merely an alias. For this reason you'll have to use the literal (String) variant. The same is true when using this added attribute in a where statement. For example: .where(review_count: 2) doesn't work, whereas .where('review_count = ?', 2) does function.
This solution makes only one query and also allows you access the values without additional queries. An example could be (index.html.erb):
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Review Count</th>
<th>Review Average</th>
</tr>
</thead>
<tbody>
<% #hospitals.each do |hospital| %>
<tr>
<td><%= hospital.id %></td>
<td><%= hospital.name %></td>
<td><%= hospital.review_count %></td>
<td><%= hospital.review_average %></td>
</tr>
<% end %>
</tbody>
</table>
First of all this is the error
First argument in form cannot contain nil or be empty
What I have is a Thread model and controller and a Answer model and Controller.
And what I am trying to do is edit the answer on the show page of the thread via a zurb Reveal Modal.
and this what my code looks like
Thread Modal
class Thread < ApplicationRecord
belongs_to :user
has_many :answers
extend FriendlyId
friendly_id :subject, use: :slugged
end
Answer Model
class Answer < ApplicationRecord
belongs_to :user
belongs_to :thread
end
Answer Controller
def edit
#thread = Thread.friendly.find(params[:thread_id])
#answer = #thread.answers.find(params[:id])
render :layout => false
end
def update
#thread = Thread.friendly.find(params[:thread_id])
#answer = #thread.answers.find(params[:id])
respond_to do |format|
if #answer.update(params[:answer].permit(:content))
format.html { redirect_to thread_path(#thread) }
format.json { render :show, status: :ok, location: #answer }
else
format.html { redirect_to #thread, alert: "Unable to save your post" }
format.json { render json: #answer.errors, status: :unprocessable_entity }
end
end
end
Show Thread
<%= render #thread.answers %>
Answer Partial
<% if current_user.present? && current_user.id == answer.user_id %>
<ul class="edit-links pull-right">
<li>
<%= link_to 'Edit', edit_thread_answer_path(answer.thread, answer), :remote => true, class: "edit", "data-open" => "editModal", "data-open-ajax" => edit_thread_answer_path(answer.thread, answer) %>
</li>
<li>
<%= link_to 'Destroy', [answer.thread, answer], method: :delete, data: { confirm: 'Are you sure?' }, class: "destroy" %>
</li>
</ul>
<% end %>
<p>
<%= simple_format answer.content %>
</p>
<div id="editModal" class="reveal" data-reveal>
<%= render :partial => 'answers/edit_form' %>
<button class="close-button" data-close aria-label="Close modal" type="button">
<span aria-hidden="true">×</span>
</button>
</div>
Answer Edit Form Partial
<h1>Edit Answer</h1>
<%= form_for([#thread, #answer]) do |f| %>
<%= f.text_area :content, :rows => "10" %>
<%= f.submit "Update", :class => "button add-thread" %>
<% end %>
I get the error when I try to open a thread#show
its says the error is here
ArgumentError in Threads#show
<%= form_for([#thread, #answer]) do |f| %>
Trace of template inclusion: app/views/answers/_answer.html.erb, app/views/threads/show.html.erb
Any suggestions ? where may the error be ?
My form needs to customize before submitting to the rails. SO, I use ajax to submit the form, but every time, rails doubles records. Anyone has any idea how it happens.
my controller here:
def create
#school_current = School.find_by_id(params[:school_id])
#quiz = Quiz.new(params[:quiz])
#quiz.from_params(params)
questions = params[:quiz][:questions_attributes]
questions.each do |index, question|
#quiz.questions.build(question)
end
respond_to do |format|
if #quiz.save
format.html { render nothing: true }
else
format.html { render action: "new" }
end
end
end
def from_params(params)
self.name = params[:quiz][:name]
self.school_id = params[:school_id]
self.description = params[:quiz][:description]
end
form:
<div id="quizzes">
<%= form_for([#school_current, #quiz], :remote => true) do |f| %>
<div class="form-inputs" id="quiz_body">
<div class="form-group">
<label>Quiz Name (required)</label>
<%= f.text_field :name, class: "form-control", placeholder: "Enter quiz name here .." %>
</div>
<div class="form-group">
<label>Quiz Description (optional)</label>
<%= f.text_area :description, :rows => 10 , :cols => 10, class: "form-control", placeholder: "Enter quiz description here .." %>
</div>
</div>
<div id="question_list">
<ol>
<%= f.fields_for :questions do |builder| %>
<li class="question_field_item"><%= render "question_fields", f: builder %></li>
<% end %>
</ol>
</div>
<div class="form-actions">
<%= f.submit "save quiz", :data => {disable_with: "Saving ..."},class: "btn btn-primary text-uppercase", id: "save_quiz" %>
</div>
<% end %>
</div>
Make sure, single AJAX request is hitting the server
the following code block might be the culprit.
respond_to do |format|
if #quiz.save
format.html { render nothing: true }
else
format.html { render action: "new" }
end
end
Try extracting the saving from respond_to's block.
if #quiz.save
respond_to do |format|
format.html { render nothing: true }
end
else
respond_to do |format|
format.html { render action: "new" }
end
end
i figured out the problem. Call question.build twice in both new and create. That's why rails duplicates the record. Remove these lines of code. It will work.
questions.each do |index, question|
#quiz.questions.build(question)
end
In my task show page, I have form for creating a response and the responses are displayed here as well. (Task has many responses)
views\tasks\show.html.erb
<%= simple_form_for([#task, Response.new]) do |f| %>
<%= f.input :is_comment, as: :hidden %>
<%= f.input :negotiate_pay, label: false %>
<%= f.input :comment_text, as: :text, input_html: {rows: 3}, label: false %>
<div class="row">
<div class="col-md-4 col-md-offset-8">
<%= f.button :submit %>
</div>
</div>
</div>
<% end %>
<br>
<div id="comments">
<%= render #responses %>
</div>
controllers\tasks_controller
def show
#task = Task.find(params[:id])
#responses = #task.responses.all
end
controllers\responses_controller
def create
#task = Task.find(params[:task_id])
#response = #task.responses.create(response_params)
#response.user_id = current_user.id
#response.is_comment = params[:is_comment]
#response.save
respond_to do |format|
format.html { redirect_to #task }
format.js
end
end
It is working alright but it doesn't show the errors in the form after validation and submit. How can I show the error message in response form which is in the show page of task. I am using a simple_form btw.
Help please
EDIT:
Models:
models\response.rb
belongs_to :user
belongs_to :task
has_many :subcomments
default_scope -> { order('created_at DESC') }
#VALIDATIONS
validates :comment_text, presence: true
validates :negotiate_pay, presence: true
You need to add the code that will display the errors to your form :
<%= simple_form_for([#task, Response.new]) do |f| %>
<% if #response.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#response.errors.count, "error") %> prohibited this response from being saved:</h2>
<ul>
<% #response.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
and you also need to tweak your create action in the responsesController:
def create
#task = Task.find(params[:task_id])
#response = #task.responses.build(response_params)
#response.user_id = current_user.id
#response.is_comment = params[:is_comment]
respond_to do |format|
if #response.save
format.html { redirect_to #task, notice: "response was createad" }
format.js
else
format.html { render :show }
end
end
also make sure you are displaying the errors inside your application.html.erb :
<% if flash[:error] -%>
<p class='alert alert-danger'><%=h flash[:error] %></p>
<% end -%>
<% if flash[:notice] -%>
<p class='alert alert-success'><%=h flash[:notice] %></p>
<% end -%>