GithubHelp home page GithubHelp logo

day18_rails's Introduction

Day18_180705

  • 좋아요 버튼 + 개수 넣고 변화하는것 까지

  • 별점주기 (좋아요버튼에 컬럼을 하나 추가하면 된다. t.integer :score )

  • 댓글 (Ajax) 입력 + 삭제 + 수정

    • 댓글입력시 글자제한( front + backend )
  • pagination (kaminari)

  • hastag (제목중복, 아이디중복 찾을 때 ) 자동완성기능?

Ajax 복습

  • MVVM : 요새 핫한 프론트엔드 자바스크립트 프레임워크
  • 모든화면을 Ajax라고 만들기엔 서버를 항상부르기엔 불편해서 만들어진 기술
  • 처음 show.html.erb 에서 url, method, data 를 지정해주고, 이 요청을 받아드릴 routes 에 해당 url을 컨트롤러에 지정
  • movies_controller 에서 좋아요랑 좋아요 취소 로직을 설정해주었다.
  • 액션명과 같은 js를 써주어 서버가 movies_controller에서 like_movie 액션이 동작할때 해당 자바스크립트가 실행되도록한다.

좋아요 버튼 + 개수

show.html.erb

<h1><%= @movie.title %></h1>
<hr>
<p> <%[email protected] %></p>
<hr>

<%= link_to 'Edit', edit_movie_path(@movie) %> |
<%= link_to 'Back', movies_path %>  | 
<% if @user_likes_movie.nil? %>
<button class="btn btn-info like">좋아요 (<span class="like-count"><%= @movie.likes.count %></span>)</button>
<% else %>
<button class="btn btn-warning like">좋아요 취소 (<span class="like-count"><%= @movie.likes.count %></span>)</button>
<% end %>
<script>
    $(document).on('ready',function(){
        $('.like').on('click', function(){
            console.log("like~!") // 확인용
            $.ajax({
               url: '/likes/<%= @movie.id %>' 
            });
        })
    });
</script>

like-movie.js.erb

var like_count = parseInt($('.like-count').text());
console.log(like_count);
//좋아요가 취소된 경우
// 좋아요 취소버튼 -> 좋아요 버튼으로 바꿔준다.
if (<%= @like.frozen? %>) {
    like_count  = like_count - 1
    $('.like').html(`좋아요(<span class='like-count'>${like_count}</span>)`).removeClass('btn-warning').addClass('btn-info'); // string Interpolation
}else{
//좋아요가 새로눌린 경우
// 좋아요 버튼 -> 좋아요 취소버튼으루~!~!
    alert("좋아요 설정되쩡!");
    like_count = like_count + 1
    $('.like').html(`좋아요 취소(<span class='like-count'>${like_count}</span>)`).removeClass('btn-info').addClass('btn-warning');
}

=> parseInt($('.like-count').text());

=> string Interpolation 얘는 `로 쓰면 여러줄도 가능 (줄바꿈가능)

변수명에 - 은 안된다. _ (under score은 가능)

movies_controller.rb

 def show
    if user_signed_in?
      @user_likes_movie = Like.where(user_id: current_user.id, movie_id: @movie.id).first
    else
      @user_likes_movie =[]
 end

댓글 입력 & 삭제

  • 댓글을 입력받을 폼을 작성

~/watcha_app/app/views/movies/show.html.erb

...
<form class="text-right comment">
    <input class="form-control comment-contents"><<br/>
    <input type="submit" value="댓글쓰기" class="btn btn-success">
</form

<ul class="list-group comment-list">
    <!--<li class="list-group-item">Cras justo odio</li>-->
</ul>
...
  • form(요소)이 제출(이벤트)될때 (이벤트 리스너)

~/watcha_app/app/views/movies/show.html.erb

...
<script>
    $(document).on('ready',function(){
        $('.like').on('click', function(){
            console.log("like~!") // 확인용
            $.ajax({
               url: '/likes/<%= @movie.id %>' 
            });
        })
        $('.comment').on('submit', function(e){
            e.preventDefault();   
            # 실제로<input type="submit"> 여기에서 page redirection이 진행되어 기존 댓글이 사라진다. 그러므로 이를 페이지 리디렉션을 방지하기위해 사용. 값이 log로 넘어오는것을 볼수있다.
            var comm = $('.comment-contents').val();
            console.log(comm);
        });
    });
</script>
...
  • form에 input(요소) 안에 있는 내용물(메소드)을 받아서

[jquery add element to element][http://api.jquery.com/append/]

~/watcha_app/app/views/movies/show.html.erb

<script>
    $(document).on('ready',function(){
        $('.like').on('click', function(){
            console.log("like~!") // 확인용
            $.ajax({
               url: '/likes/<%= @movie.id %>' 
            });
        })
        $('.comment').on('submit', function(e){
            e.preventDefault();
            var comm = $('.comment-contents').val();
            console.log(comm);
            $('.comment-list').append(comm);
        });
    });
</script>

한줄씩 쓰게 하기 위해서 다음의 코드로 수정한다.

 $('.comment-list').append(`<li class="list-group-item">${comm}</li>`);
 $('.comment-contents').val(''); // 댓글입력창에 존재하는 값 날리기용

하지만, 첫번째 쓴것을 맨위에 올리기위해 .append 가 아닌 .prepend로 사용한다.

  • comment model 만들기
    • column : user_id, movie_id, contents
    • association :
      • movie(1) - comment (N)
      • user(1) - comment(N)
    • url("/movies/:movie_id/comments", method: post)인 ajax 코드 짜보기
<h1><%= @movie.title %></h1>
<hr>
<p> <%[email protected] %></p>
<hr>

<%= link_to 'Edit', edit_movie_path(@movie) %> |
<%= link_to 'Back', movies_path %>  | 
<% if @user_likes_movie.nil? %>
<button class="btn btn-info like">좋아요 (<span class="like-count"><%= @movie.likes.count %></span>)</button>
<% else %>
<button class="btn btn-warning like">좋아요 취소 (<span class="like-count"><%= @movie.likes.count %></span>)</button>
<% end %>
<hr>

<form class="text-right comment">
    <input class="form-control comment-contents"><br/>
    <input type="submit" value="댓글쓰기" class="btn btn-success">
</form>
<hr>
<h3>댓글</h3>
<ul class="list-group comment-list">
    <!--<li class="list-group-item">Cras justo odio</li>-->
</ul>
<hr>
<script>
    $(document).on('ready',function(){
        $('.like').on('click', function(){
            console.log("like~!") // 확인용
            $.ajax({
               url: '/likes/<%= @movie.id %>' 
            });
        })
        $('.comment').on('submit', function(e){
            e.preventDefault();
            var comm = $('.comment-contents').val();
            console.log(comm);
            $.ajax({
                url: '/movies/<%= @movie.id %>/comments',
                data: {contents: comm},
                method: 'post'
            });
            $('.comment-list').prepend(`<li class="list-group-item">${comm}</li>`);
            $('.comment-contents').val(''); // 값날리기용
        });
        
        
    });
</script>

! 라우팅 참고 => Nested Resources

routes.rb

    member do
      get '/comments' => 'movies#create_comment'
    end

=> comments_movie GET /movies/:id/comments(.:format) movies#create_comment

이렇게 된다.

AbstractController::ActionNotFound (The action 'create_comment' could not be found for MoviesController): controller에 액션이 없기 때문에 에러일것이다.

  • 보낼때에는 내용물, 현재보고있는 movie의 id값도 같이 보낸다

movies_controller

 before_action :set_movie, only: [:show, :edit, :update, :destroy, :create_comment]

  def create_comment
    #  @movie = Movie.find(params[:id]) <- set_movie 랑 같으니까
    @comment =Comment.create(user_id: current_user.id, movie_id: @movie.id, contents: params[:contents])
      	   #  @comment =Comment.create(user_id: current_user.id, movie_id: @movie.id)랑
           # movie.comments.new(user_id: current_user.id).save 와 같은 말
  end

이렇게 컨트롤러에서 넘기는 contents 는 show.html.erb 에 있는 data: {contents: comm}, 라는 Ajax의 코드로 데이터가 넘어온다.

  • 서버에서 저장하고 response로 보내줄 js.erb 파일을 작성한다.

~/watcha_app/app/views/movies/create_comment.js.erb 파일을 생성하고, 안에 show.html.erb에 있던 댓글 작성기능 코드를 잘라온다

$('.comment-list').prepend(`<li class="list-group-item"><%= @comment.contents %></li>`);
$('.comment-contents').val(''); // 값날리기용
alert('댓글 등록이 완료되었습니다.');
  • js.erb 파일에서는 댓글이 표시될 영역에 등록된 댓글의 내용을 추가해준다.

show.html.erb 에 기존에 등록되어있는 댓글을 출력하는 코드

<ul class="list-group comment-list">
    <!--기존에 등록되어있는 댓글 출력-->
    <% @movie.comments.reverse.each do |comment| %>
        <li class="list-group-item d-flex justify-content-between"><%= comment.contents %> ( <%= comment.user.email %> )
    <button class="btn btn-danger">삭제</button></li>
    <% end %>
</ul>

[정렬-부트스트랩][https://getbootstrap.com/docs/4.1/utilities/flex/#justify-content]

  • 댓글에 있는 삭제버튼(요소)을 누르면(이벤트 리스너) 해당 댓글이 눈에 안보이게 되고, (이벤트 핸들러)

    views/movies/show.html.erb

<script>
    ...
		$('.destroy-comment').on('click', function(){
            console.log("destroyed!!!!");   
            $(this).parent().remove();
        });
	...
</script>	

button을 감싸는 태그의 바로 상위태그(

  • ) 불러오는거 => parent

    상위태그 모두 다 불러오려면 =>parents

    parents().find() 이런식으로도 쓸수는 있겠지.

    • 실제 db에서도 삭제가 된다.(ajax코드)

      <ul class="list-group comment-list">
          <!--<li class="list-group-item">Cras justo odio</li>-->
          <!--기존에 등록되어있는 댓글 출력-->
          <% @movie.comments.reverse.each do |comment| %>
              <li class="list-group-item d-flex justify-content-between">
                  <%= comment.contents %> ( <%= comment.user.email %> )
              <button data-id="<%= comment.id %>" class="btn btn-danger destroy-comment">삭제</button></li>
          <% end %>
      </ul>
      <hr>
      <script>
      ...
              $('.destroy-comment').on('click', function(){
                  console.log("destroyed!!!!");   
                  $(this).parent().remove();
                  var comment_id = $(this).attr('data-id');
                //  $(this).data('id'); //이렇게도 뽑아낼수있으data-id로 썼던 이유.
                console.log(comment_id);
                  $.ajax({
                      url: "/movies/comments/" + comment_id //resouceful routing,
                      method: "delete"
                  })
              });
          });
      </script>

      GET https://my-second-rails-app-binn02.c9users.io/movies/comments/4 404 (Not Found) 라는 에러뜬다.

      왜냐하면 저 url로 연결되는게 없기 때문이다.

      routes.rb

          collection do
            delete '/comments/:comment_id' => 'movies#destroy_comment'
          end

      이러면 또 액션이 없어서 에러난다.

      movies_controller.rb

        def destroy_comment
           @comment = Comment.find(params[:comment_id]).destroy
        end

      액션이 실행될때만 js를 돌려주기 위해서 파일을 분리하자

      destroy_comment.js.erb

      alert("댓글이 삭제되었습니다.");
      $('.comment-<%[email protected] %>').remove();

      show.html.erb

      <li class="comment-<%= comment.id %> // parent로 안하고 바로 받기위해서 class설정
    • 새로 등록하는 댓글들에는 삭제버튼이 안나오는 에러를 해결하자

    ~/watcha_app/app/views/movies/create_comment.js.erb

    $('.comment-list').prepend(`<li class="comment-<%= @comment.id %> list-group-item d-flex justify-content-between">
                <%= @comment.contents %> ( <%= @comment.user.email %> )
                <button data-id="<%= @comment.id %>" class="btn btn-danger destroy-comment">삭제</button>
                </li>`);
    $('.comment-contents').val(''); // 값날리기용
    alert('댓글 등록이 완료되었습니다.');

    아 근데 또 문제있어

    새로 등록한 댓글에 삭제버튼이 동작하지 않는다. <- 자바스크립트로 추가된 친구는 $('.destroy-comment').on('click', function(){ 얘가 동작하지 않는다

    show.html.erb

      $(document).on('click','.destroy-comment', function(){ 
                console.log("destroyed!!!!");   
                var comment_id = $(this).attr('data-id');
              //  $(this).data('id'); //이렇게도 뽑아낼수있으data-id로 썼던 이유.
              console.log(comment_id);
                $.ajax({
                    url: "/movies/comments/" + comment_id, //resouceful routing
                    method: "delete"
                })
            });

    $(document).on('click','.destroy_comment', function(){ : dom-tree를 다시 읽어서 '.destroy_comment'를 찾자. (쉼표로 끊어주었기에 목적지를 지정한거라 생각하자 )

    이거는 create_comment.js.erb 에서 버튼의 클래스가 class="btn btn-danger destroy-comment 이기때문에!

    댓글수정

    <li class="comment-<%= comment.id %> list-group-item d-flex justify-content-between">
       <span class="comment-detail"><%= comment.contents %></span>
         <div>
          <button data-id="<%= comment.id %>" class="btn btn-warning text-white edit-comment">수정</button>
           <button data-id="<%= comment.id %>" class="btn btn-danger destroy-comment">삭제</button>
         </div>
    </li>
                            
    ...
                            
     <script>
    	..
    	$('.edit-comment').on('click', function() {
               var detail = $('.comment-detail').text();
               console.log(detail);
            });
        ..
    </script>

    이거는 모든 댓글들을 다 합쳐서 보여준다

    • 수정버튼(요소)을 클릭하면 (이벤트 리스너) 댓글이 있던 부분이 입력창으로 바뀌면서 원래 있던 댓글의 내용이 입력창에 들어간다.(이벤트 핸들러)

    • 수정버튼은 확인 버튼으로 바뀐다.

    show.html.erb

    <li class="comment-<%= comment.id %> list-group-item d-flex justify-content-between">
      <span class="comment-detail-<%= comment.id%>"><%= comment.contents %></span>
          <div>
            <button data-id="<%= comment.id %>" class="btn btn-warning text-white edit-comment">수정</button>
            <button data-id="<%= comment.id %>" class="btn btn-danger destroy-comment">삭제</button>
          </div>
    </li> 
    ...
    <script>
      $(document).on('click','.edit-comment', function() { //수정버튼(요소) 클릭하면
           
            var comment_id = $(this).data('id');
            var edit_comment = $(`.comment-detail-${comment_id}`);
            var contents = edit_comment.text().trim();
            edit_comment.html(`
            <input type="text" value="${contents}" class="form-control edit-comment-${comment_id}">`);
                //(이벤트 리스너) 댓글이 있던 부분이 입력창으로 바뀌면서원래 있던 댓글의 내용이 입력창에 들어간다.(이벤트 핸들러)
              $(this).text("확인").removeClass("edit-comment btn-warning").addClass("update-comment btn-dark"); //수정버튼은 확인 버튼으로 바뀐다.
            });
    ...               
    </script>
    //   console.log($(this).parent().parent().find('.comment-detail'))); //li 에서 comment-detail을 찾기위해서
    //  하지만 넘 거지같으므로  <span class="comment-detail-<%= comment.id%>"> 를 걍 해준다
    

    • 내용 수정 후 확인버튼을 클릭하면 입력창에 있던 내용물이 댓글의 원래 형태로 바뀌고 확인버튼은 다시 수정버튼으로 바뀐다.
    <script>
        ...
    	  $(document).on('click','.update-comment' ,function(){ // 내용 수정  확인버튼을 클릭하면
               console.log("update"); 
              var comment_id = $(this).data('id');
              var comment_form = $(`.edit-comment-${comment_id}`)
            //  console.log(comment_form.val());
              var edit_comment = $(`.comment-detail-${comment_id}`);
              edit_comment.html(comment_form.val()); //입력창에 있던 내용물이 댓글의 원래 형태로 바뀌고
                $(this).text("수정").removeClass("update-comment btn-dark").addClass("edit-comment btn-warning"); //확인버튼은 다시 수정버튼으로 바뀐다.
            });
    	...
    </script>
    • 입력창에 있던 내용물을 Ajax로 서버에 요청을 보낸다.
           $.ajax({
                    url: "/movies/comments/" + comment_id,
                    data:{contents: comment_form.val()},
                    method: "PATCH"
                })

    404 (Not Found) 에러 발생 한다.

    • 서버에서는 해당 댓글을 찾아 내용을 업데이트 한다.

    routes.rb

        collection do
          patch '/comments/:comment_id' => 'movies#update_comment'
        end

    movie_controller.rb

      def update_comment
        @comment = Comment.find(params[:comment_id])
        @comment.update(contents: params[:contents])
      end

    @comment = Comment.find(params[:comment_id]).update(contents: params[:contents]) 로 하면

    @comment에 T / F 형태로 담긴다.

    update_comment.js.erb

    alert("수정이 완료되었습니다.");
    var edit_comment = $('.comment-detail-<%[email protected]%>');
    edit_comment.html('<%= @comment.contents %>');
    $('.update-comment').text("수정").removeClass("update-comment btn-dark").addClass("edit-comment btn-warning");

    수정 문제 2가지

    1. 새글에 수정버튼이 없다

    show.html.erb

     <li class="comment-<%= comment.id %> list-group-item d-flex justify-content-between">
               <span class="comment-detail-<%= comment.id%>"><%= comment.contents %></span>
                <div>
                    <button data-id="<%= comment.id %>" class="btn btn-warning text-white edit-comment">수정</button>
                    <button data-id="<%= comment.id %>" class="btn btn-danger destroy-comment">삭제</button>
                </div>
    </li>    

    이 부분을 복사해서 create_comment.js.erb 에서 지역변수로만 바꿔서 적용해주자

    create_comment.js.erb

    $('.comment-list').prepend(`<li class="comment-<%= @comment.id %> list-group-item d-flex justify-content-between">
               <span class="comment-detail-<%= @comment.id%>"><%= @comment.contents %></span>
                <div>
                    <button data-id="<%= @comment.id %>" class="btn btn-warning text-white edit-comment">수정</button>
                    <button data-id="<%= @comment.id %>" class="btn btn-danger destroy-comment">삭제</button>
                </div>
            </li>`);
    $('.comment-contents').val(''); // 값날리기용
    alert('댓글 등록이 완료되었습니다.');

    사실 이것을 렌더로 처리하면 더 쉽고 간편해진다!!

    interpolation을 안써도 멀티라인으로 코드를 쓰려면 `` 를 써야한다.

    1. 수정버튼이 여러개 열린다.
    • 수정 버튼을 누르면
    • 전체 문서 중에서 update-comment 클래스를 가진 버튼 이 있는 경우 진행을 이벤트 핸들러를 끝내고 return false;, 하지 말자 라고 alert을 띄어주자
    $(document).on('click','.edit-comment', function() {
    	var comment_id = $(this).data('id');
    	var edit_comment = $(`.comment-detail-${comment_id}`);
    	var contents = edit_comment.text().trim();
    	var confirm_button = $('.update-comment').length;
    	console.log(confirm_button);
            if(confirm_button > 0){
                alert("수정 버튼은 한개만 눌러주세요.");
                return false;
            }else{
            edit_comment.html(`
            <input type="text" value="${contents}" class="form-control edit-comment-${comment_id}">`);
            $(this).text("확인").removeClass("edit-comment btn-warning").addClass("update-comment btn-dark");    
            }});

    ORM을 사용하는 이유

    : db에 있는 친구들을 객체로 쓰듯이 쓰려고 !

    • 현재유저가 등록한 댓글들

      : current_user.comments

    • 영화 하나가 가지고 있는 댓글들

      : movie.comments

    • 코멘트 하나에 달린 무비

      : comment.movie

    day18_rails's People

    Contributors

    ubinn avatar

    Watchers

     avatar

    Recommend Projects

    • React photo React

      A declarative, efficient, and flexible JavaScript library for building user interfaces.

    • Vue.js photo Vue.js

      🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

    • Typescript photo Typescript

      TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

    • TensorFlow photo TensorFlow

      An Open Source Machine Learning Framework for Everyone

    • Django photo Django

      The Web framework for perfectionists with deadlines.

    • D3 photo D3

      Bring data to life with SVG, Canvas and HTML. 📊📈🎉

    Recommend Topics

    • javascript

      JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

    • web

      Some thing interesting about web. New door for the world.

    • server

      A server is a program made to process requests and deliver data to clients.

    • Machine learning

      Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

    • Game

      Some thing interesting about game, make everyone happy.

    Recommend Org

    • Facebook photo Facebook

      We are working to build community through open source technology. NB: members must have two-factor auth.

    • Microsoft photo Microsoft

      Open source projects and samples from Microsoft.

    • Google photo Google

      Google ❤️ Open Source for everyone.

    • D3 photo D3

      Data-Driven Documents codes.