Skip to content

Commit

Permalink
Merge pull request #5 from metacorn/Part_6_AJAX_in_Rails
Browse files Browse the repository at this point in the history
Part 6. AJAX in Rails.
  • Loading branch information
metacorn authored Jun 26, 2019
2 parents d4bca0f + 3cd0a05 commit 5d7f39c
Show file tree
Hide file tree
Showing 44 changed files with 744 additions and 150 deletions.
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ gem 'uglifier', '>= 1.3.0'
# gem 'mini_racer', platforms: :ruby
# Devise
gem 'devise'
# JS
gem 'jquery-rails'

# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
Expand Down Expand Up @@ -61,7 +63,9 @@ end
group :test do
# Adds support for Capybara system testing and selenium driver
gem 'capybara', '>= 2.15'
gem 'selenium-webdriver'
# Easy installation and use of chromedriver to run system tests with Chrome
gem 'webdrivers', '~> 4.0'
gem 'shoulda-matchers'
gem 'rails-controller-testing'
gem 'launchy'
Expand Down
19 changes: 18 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (~> 1.5)
xpath (~> 3.2)
childprocess (1.0.1)
rake (< 13.0)
coffee-rails (4.2.2)
coffee-script (>= 2.2.0)
railties (>= 4.0.0)
Expand Down Expand Up @@ -89,6 +91,10 @@ GEM
concurrent-ruby (~> 1.0)
jbuilder (2.9.1)
activesupport (>= 4.2.0)
jquery-rails (4.3.5)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
launchy (2.4.3)
addressable (~> 2.3)
listen (3.1.5)
Expand All @@ -107,7 +113,7 @@ GEM
mini_mime (1.0.1)
mini_portile2 (2.4.0)
minitest (5.11.3)
msgpack (1.2.10)
msgpack (1.3.0)
nio4r (2.3.1)
nokogiri (1.10.3)
mini_portile2 (~> 2.4.0)
Expand Down Expand Up @@ -172,6 +178,7 @@ GEM
rspec-support (~> 3.8.0)
rspec-support (3.8.2)
ruby_dep (1.5.0)
rubyzip (1.2.3)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
Expand All @@ -183,6 +190,9 @@ GEM
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
selenium-webdriver (3.142.3)
childprocess (>= 0.5, < 2.0)
rubyzip (~> 1.2, >= 1.2.2)
shoulda-matchers (4.1.0)
activesupport (>= 4.2.0)
slim (4.0.1)
Expand Down Expand Up @@ -221,6 +231,10 @@ GEM
activemodel (>= 5.0)
bindex (>= 0.4.0)
railties (>= 5.0)
webdrivers (4.0.1)
nokogiri (~> 1.6)
rubyzip (~> 1.0)
selenium-webdriver (>= 3.0, < 4.0)
websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.4)
Expand All @@ -238,6 +252,7 @@ DEPENDENCIES
devise
factory_bot_rails
jbuilder (~> 2.5)
jquery-rails
launchy
listen (>= 3.0.5, < 3.2)
pg (>= 0.18, < 2.0)
Expand All @@ -246,6 +261,7 @@ DEPENDENCIES
rails-controller-testing
rspec-rails (~> 3.8)
sass-rails (~> 5.0)
selenium-webdriver
shoulda-matchers
slim-rails
spring
Expand All @@ -254,6 +270,7 @@ DEPENDENCIES
tzinfo-data
uglifier (>= 1.3.0)
web-console (>= 3.3.0)
webdrivers (~> 4.0)

RUBY VERSION
ruby 2.6.3p62
Expand Down
3 changes: 0 additions & 3 deletions app/assets/javascripts/answers.coffee

This file was deleted.

8 changes: 8 additions & 0 deletions app/assets/javascripts/answers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
$(document).on('turbolinks:load', function() {
$('.answers').on('click', '.edit-answer-links', function(e) {
e.preventDefault()
$(this).hide()
var answerId = $(this).data('answerId')
$('form#edit-answer-' + answerId).show()
})
})
1 change: 1 addition & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require jquery3
//= require_tree .
3 changes: 0 additions & 3 deletions app/assets/javascripts/questions.coffee

This file was deleted.

8 changes: 8 additions & 0 deletions app/assets/javascripts/questions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
$(document).on('turbolinks:load', function() {
$('.edit-question-link').on('click', function(e) {
e.preventDefault()
$(this).hide()
var questionId = $(this).data('questionId')
$('form#edit-question-' + questionId).show()
})
})
3 changes: 3 additions & 0 deletions app/assets/stylesheets/common.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.hidden {
display: none
}
29 changes: 19 additions & 10 deletions app/controllers/answers_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class AnswersController < ApplicationController
before_action :authenticate_user!
before_action :set_answer, only: %i[update destroy mark]

def new
@answer = Answer.new
Expand All @@ -9,25 +10,33 @@ def create
@question = Question.find(params[:question_id])
@answer = @question.answers.new(answer_params)
@answer.user = current_user
@answer.save
end

if @answer.save
redirect_to @question, notice: "Your answer was saved."
else
flash[:alert] = "Your answer was not saved."
render "questions/show"
end
def update
return head :forbidden unless current_user.owner?(@answer)
@answer.update(answer_params)
@question = @answer.question
end

def destroy
answer = Answer.find(params[:id])
return head :forbidden unless current_user.owned?(answer)
answer.destroy
redirect_to answer.question, notice: "Your answer was successfully deleted!"
return head :forbidden unless current_user.owner?(@answer)
@answer.destroy
end

def mark
return head :forbidden unless current_user.owner?(@answer.question)
@answer.mark_as_best
@question = @answer.question
end

private

def answer_params
params.require(:answer).permit(:body)
end

def set_answer
@answer = Answer.find(params[:id])
end
end
9 changes: 3 additions & 6 deletions app/controllers/questions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,12 @@ def create
end

def update
if @question.update(question_params)
redirect_to @question
else
render :edit
end
return head :forbidden unless current_user.owner?(@question)
@question.update(question_params)
end

def destroy
return head :forbidden unless current_user.owned?(@question)
return head :forbidden unless current_user.owner?(@question)
@question.destroy
redirect_to questions_path, notice: "Your question was successfully deleted!"
end
Expand Down
26 changes: 26 additions & 0 deletions app/models/answer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@ class Answer < ApplicationRecord
belongs_to :user
belongs_to :question

default_scope { order(best: :desc, created_at: :asc) }
scope :best, -> { where(best: true) }

validates :body, presence: true,
length: { minimum: 50 }
validate :only_one_best_per_question

def mark_as_best
transaction do
question.answers.best.update_all(best: false)
self.update!(best: true)
end
end

private

def only_one_best_per_question
return unless best?
return unless question&.answers.best.any?

if persisted?
# update
errors.add(:best, :there_is_the_best_answer_in_question_already) if changes['best'] == [false, true]
else
# create
errors.add(:best, :there_is_the_best_answer_in_question_already)
end
end
end
2 changes: 1 addition & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class User < ApplicationRecord
:rememberable,
:validatable

def owned?(content)
def owner?(content)
content.user_id == id
end
end
14 changes: 12 additions & 2 deletions app/views/answers/_answer.html.slim
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
p= answer.body
p= render(partial: 'shared/manage_answer_links', locals: { answer: answer }) if current_user&.owned?(answer)
div id="answer_#{answer.id}"
- if answer.persisted?
- if answer.best?
div id="best-answer"
h4 The best answer
p= answer.body
p= render(partial: 'shared/answer_links', locals: { answer: answer })

= form_with model: answer, class: 'hidden', html: { id: "edit-answer-#{answer.id}" } do |f|
= f.label :body, 'Your answer'
= f.text_area :body
= f.submit 'Update'
5 changes: 5 additions & 0 deletions app/views/answers/create.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
$('.answer-errors').html('<%= render 'shared/errors', resource: @answer %>')
<% if @answer.persisted? %>
$('.answers').append('<%= j render @answer %>')
$('.new-answer #answer_body').val('')
<% end %>
1 change: 1 addition & 0 deletions app/views/answers/destroy.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$('#answer_' + <%= @answer.id %>).remove()
1 change: 1 addition & 0 deletions app/views/answers/mark.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$('.answers').html('<%= j render @question.answers %>')
4 changes: 4 additions & 0 deletions app/views/answers/update.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$('.answer-errors').html('<%= render 'shared/errors', resource: @answer %>')
<% if @answer.errors.empty? %>
$("#answer_" + <%= @answer.id %>).replaceWith('<%= j render @answer %>')
<% end %>
4 changes: 1 addition & 3 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
</head>

<body>
<p align="right">
<%= render 'shared/user_nav' %>
</p>
<%= render 'shared/user_nav' %>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%= yield %>
Expand Down
11 changes: 11 additions & 0 deletions app/views/questions/_question_for_show.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
h4= @question.title
p= @question.body
=render(partial: 'shared/question_links', locals: { question: @question }) if current_user&.owner?(@question)
.question-errors
=render 'shared/errors', resource: @question
= form_with model: @question, class: 'hidden', html: { id: "edit-question-#{@question.id}" } do |f|
= f.label :title
= f.text_field :title
= f.label :body, 'Body'
= f.text_area :body
= f.submit 'Update'
16 changes: 8 additions & 8 deletions app/views/questions/show.html.slim
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
= render 'shared/errors', resource: @answer

=render(partial: 'shared/manage_question_links', locals: { question: @question }) if current_user&.owned?(@question)
h4= @question.title
p= @question.body
p Answers
p= render @question.reload.answers
div id="question_#{@question.id}"
= render 'question_for_show', resource: @question
h4 Answers
div.answers
= render @question.answers
p Your answer
= form_with model: @answer, url: question_answers_path(@question), local: true do |f|
.answer-errors
= render 'shared/errors', resource: @answer
= form_with model: [@question, @answer], class: 'new-answer' do |f|
= f.label :body
= f.text_area :body
= f.submit 'Leave'
4 changes: 4 additions & 0 deletions app/views/questions/update.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$('.question-errors').html('<%= render 'shared/errors', resource: @question %>')
<% if !@question.errors.present? %>
$("#question_" + <%= @question.id %>).html('<%= j render 'question_for_show', resource: @question %>')
<% end %>
7 changes: 7 additions & 0 deletions app/views/shared/_answer_links.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
p
=> link_to('Mark as the best', mark_answer_path(answer), method: :post, remote: true) \
if current_user&.owner?(answer.question) && !answer.best?
=> link_to('Edit', '#', class: 'edit-answer-links', data: { answer_id: answer.id }) \
if current_user&.owner?(answer)
= link_to('Delete', answer_path(answer), method: :delete, remote: true) \
if current_user&.owner?(answer)
1 change: 0 additions & 1 deletion app/views/shared/_manage_answer_links.html.slim

This file was deleted.

1 change: 0 additions & 1 deletion app/views/shared/_manage_question_links.html.slim

This file was deleted.

3 changes: 3 additions & 0 deletions app/views/shared/_question_links.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
p
=> link_to 'Edit', '#', class: 'edit-question-link', data: { question_id: question.id }
= link_to 'Delete', question_path(question), method: :delete
8 changes: 7 additions & 1 deletion app/views/shared/_user_nav.html.slim
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
p= link_to 'Log out', destroy_user_session_path, method: :delete
- if current_user
p= link_to 'Log out', destroy_user_session_path, method: :delete
- else
p
= link_to 'Log in', new_user_session_path
|&nbsp;|&nbsp;
= link_to 'Sign up', new_user_registration_path
6 changes: 5 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
root to: "questions#index"

resources :questions do
resources :answers, shallow: true, except: %i[index show]
resources :answers, shallow: true, except: %i[index show] do
member do
post :mark
end
end
end
end
5 changes: 5 additions & 0 deletions db/migrate/20190624142356_add_best_to_answers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddBestToAnswers < ActiveRecord::Migration[5.2]
def change
add_column :answers, :best, :boolean, default: false
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2019_06_18_204807) do
ActiveRecord::Schema.define(version: 2019_06_24_142356) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand All @@ -21,6 +21,7 @@
t.datetime "updated_at", null: false
t.bigint "question_id"
t.bigint "user_id"
t.boolean "best", default: false
t.index ["question_id"], name: "index_answers_on_question_id"
t.index ["user_id"], name: "index_answers_on_user_id"
end
Expand Down
Loading

0 comments on commit 5d7f39c

Please sign in to comment.