게시판 - 회원가입

소스코드

이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.

이 게시물의 소스코드는 게시판 만들기 / 게시판 - 스타일 수정에서 이어집니다.

board.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.

git reset --hard
git pull
git reset --hard e632760
git reset --soft 0e0531f
npm install
atom .

board.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 board.git 을 clone 합니다.

git clone https://github.com/a-mean-blogger/board.git
cd board
git reset --hard e632760
git reset --soft 0e0531f
npm install
atom .

- Github에서 소스코드 보기: https://github.com/a-mean-blogger/board/tree/e6327605932a55081891585fb555a0d90fdf79c0


회원가입을 만들어 봅시다!
사실 회원가입은 주소록, 게시물과 다르게 특별한 건 아니고, user modelCRUD 중 create이 바로 회원가입입니다.

게시판 만들기 강의에서는  user CRUD 중에서 생성, 열람, 수정만 구현하고 삭제는 만들지 않습니다.
나중에 user(이용자)가 post(게시물)를 생성하게 될텐데, post가 생성된 이후에 user를 삭제하게 되면 user와 post간의 연결이 끊어지게 됩니다. 이때 이걸 해결하는 방법이 여러가지가 있는데.. 그건 이 강좌에서 다루지 않을 예정이기 때문에, user의 삭제 기능은 없습니다.

주소록, 게시물에 이어 이번이 3번째 비슷한 CRUD인데요, user는 password가 필요하고 회원가입, 정보 수정시에 인증이 필요하므로 model이 좀 더 복잡하게 됩니다. (route은 여전히 크게 달라지지 않습니다.)

이번에 만들 회원가입 페이지의 디자인은 아래와 같습니다.

이번엔 front end와 back end를 한번에 만들겠습니다.

폴더구조


주황색은 변경된 파일, 녹색은 새로 생성된 파일, 회색은 변화가 없는 파일입니다.

코드 - js

model부터 살펴봅시다.

// models/User.js

var mongoose = require("mongoose");

// schema // 1
var userSchema = mongoose.Schema({
 username:{type:String, required:[true,"Username is required!"], unique:true},
 password:{type:String, required:[true,"Password is required!"], select:false},
 name:{type:String, required:[true,"Name is required!"]},
 email:{type:String}
},{
 toObject:{virtuals:true}
});

// virtuals // 2
userSchema.virtual("passwordConfirmation")
.get(function(){ return this._passwordConfirmation; })
.set(function(value){ this._passwordConfirmation=value; });

userSchema.virtual("originalPassword")
.get(function(){ return this._originalPassword; })
.set(function(value){ this._originalPassword=value; });

userSchema.virtual("currentPassword")
.get(function(){ return this._currentPassword; })
.set(function(value){ this._currentPassword=value; });

userSchema.virtual("newPassword")
.get(function(){ return this._newPassword; })
.set(function(value){ this._newPassword=value; });

// password validation // 3
userSchema.path("password").validate(function(v) {
 var user = this; // 3-1

 // create user // 3-3
 if(user.isNew){ // 3-2
  if(!user.passwordConfirmation){
   user.invalidate("passwordConfirmation", "Password Confirmation is required!");
  }
  if(user.password !== user.passwordConfirmation) {
   user.invalidate("passwordConfirmation", "Password Confirmation does not matched!");
  }
 }

 // update user // 3-4
 if(!user.isNew){
  if(!user.currentPassword){
   user.invalidate("currentPassword", "Current Password is required!");
  }
  if(user.currentPassword && user.currentPassword != user.originalPassword){
   user.invalidate("currentPassword", "Current Password is invalid!");
  }
  if(user.newPassword !== user.passwordConfirmation) {
   user.invalidate("passwordConfirmation", "Password Confirmation does not matched!");
  }
 }
});

// model & export
var User = mongoose.model("user",userSchema);
module.exports = User;

1. schema : require 에 true 대신 배열이 들어갔습니다. 첫번째는 true/false 값이고, 두번째는 에러메세지입니다. 그냥 true/false을 넣을 경우 기본 에러메세지가 나오고, 배열을 사용해서 custom(사용자정의) 에러메세지를 만들 수 있습니다.
password에는 select:false가 추가되었습니다. 기본설정은 자동으로 select:true인데, schema항목을 DB에서 읽어옵니다. select:false로 설정하면 DB에서 값을 읽어 올때 해당 값을 읽어오라고 하는 경우에만 값을 읽어오게 됩니다. 비밀번호는 중요하기 때문에 기본적으로 DB에서 값을 읽어오지 않게 설정했습니다. 값을 읽어오는 방법은 아래 route부분을 설명할때 나옵니다.

2. DB에 저장되는 값은 password인데, 회원가입, 정보 수정시에는 위 값들이 필요합니다. DB에 저장되지 않아도 되는 정보들은 virtual로 만들어 줍니다.

3. DB에 정보를 생성, 수정하기 전에 mongoose가 값이 유효(valid)한지 확인(validate)을 하게 되는데 password항목에 custom(사용자정의) validation 함수를 지정할 수 있습니다. virtual들은 직접 validation이 안되기 때문에(DB에 값을 저장하지 않으니까 어찌보면 당연합니다) password에서 값을 확인하도록 했습니다.

3-1. validation callback 함수 속에서 this는 user model입니다.

3-2. model.isNew 항목이 true이면 새로 생긴 model(DB에 한번도 기록되지 않았던 model) 즉, 새로 생성되는 user이며, 값이 false이면 DB에서 읽어 온 model 즉, 회원정보를 수정하는 경우입니다.

3-3. 회원가입의 경우 password confirmation값이 없는 경우, password와 password confirmation값이 다른 경우에 유효하지않음처리(invalidate)를 하게 됩니다. model.invalidate함수를 사용하며, 첫번째는 인자로 항목이름, 두번째 인자로 에러메세지를 받습니다.

3-4. 회원정보 수정의 경우 current password값이 없는 경우, current password값이 original password랑 다른 경우, new password 와 password confirmation값이 다른 경우 invalidate합시다. 회원정보 수정시에는 항상 비밀번호를 수정하는 것은 아니기 때문에 new password와 password confirmation값이 없어도 에러는 아닙니다.

다음으로 route을 살펴봅시다.

// routes/users.js

var express = require("express");
var router = express.Router();
var User  = require("../models/User");

// Index // 1
router.get("/", function(req, res){
 User.find({})
 .sort({username:1})
 .exec(function(err, users){
  if(err) return res.json(err);
  res.render("users/index", {users:users});
 });
});

// New
router.get("/new", function(req, res){
 res.render("users/new", {user:{}});
});

// create
router.post("/", function(req, res){
 User.create(req.body, function(err, user){
  if(err) return res.json(err);
  res.redirect("/users");
 });
});

// show
router.get("/:username", function(req, res){
 User.findOne({username:req.params.username}, function(err, user){
  if(err) return res.json(err);
  res.render("users/show", {user:user});
 });
});

// edit
router.get("/:username/edit", function(req, res){
 User.findOne({username:req.params.username}, function(err, user){
  if(err) return res.json(err);
  res.render("users/edit", {user:user});
 });
});

// update // 2
router.put("/:username",function(req, res, next){
 User.findOne({username:req.params.username}) // 2-1
 .select("password") // 2-2
 .exec(function(err, user){
  if(err) return res.json(err);

  // update user object
  user.originalPassword = user.password;
  user.password = req.body.newPassword? req.body.newPassword : user.password; // 2-3
  for(var p in req.body){ // 2-4
   user[p] = req.body[p];
  }

  // save updated user
  user.save(function(err, user){
   if(err) return res.json(err);
   res.redirect("/users/"+req.params.username);
  });
 });
});

module.exports = router;

7 actions에서 destroy만 빠졌습니다. 나머지는 기본형태와 같고, index와 update만 살펴봅시다.

1. User.find에는 찾을 조건({} = 모든 값)이 들어가고, sort(정렬) 함수를 넣어주기 위해서 callback 함수없이 괄호가 닫혔습니다.
sort 함수에는 {username:1} 이 들어가서 username을 기준으로 오름차순(asc)으로 정렬하고 있습니다.(-1을 넣으면 내림차순(desc)이 됩니다.)

callback 함수가 find 함수 밖으로 나오게 되면, exec(callback_함수)를 사용합니다.

sort가 없을때 (callback 함수가 find 함수에 인자로 들어감)

User.find({}, function(err, users){
  // ...
})

sort가 있을때 (callback 함수가 exec 함수에 인자로 들어감)

User.find({})
.sort({username:1})
.exec(function(err, users){
  // ...
})

sort가 있을때와 없을때 모양을 비교해 보시면 쉽게 이해할 수 있습니다.

sort말고도 다양한 함수들이 끼어들 수 있는데요, 이러한 경우에 exec를 사용합니다.

2-1. 이번에는 findOneAndUpdate대신에 findOne으로 값을 찾은 후에 값을 수정하고 user.save함수로 값을 저장합니다. 단순히 값을 바꾸는 것이 아니라 user.password를 조건에 맞게 바꿔주어야 하기 때문이죠.

2-2. select 함수를 이용하면 DB에서 어떤 항목을 선택할지, 안할지를 정할 수 있습니다. user schema에서 password의 select을 false로 설정했으니 DB에 password가 있더라도 기본적으로 password를 읽어오지 않게 되는데, .select함수로 password 항목을 선택하고 있습니다. 참고로 select함수로 기본적으로 읽어오게 되어 있는 항목을 안 읽어오게 할 수도 있는데 이때는 항목이름 앞에 -를 붙이면 됩니다. 또한 하나의 select함수로 여러 항목을 동시에 정할 수도 있는데, 예를 들어 password를 읽어오고, name을 안 읽어오게 하고 싶다면 .select("password -name")를 입력하면 되겠습니다.

2-3. user의 update는 두가지로 나눌 수 있는데, password를 업데이트 하는 경우와, password를 업데이트 하지 않는 경우에 따라 user.password의 값이 바뀝니다.

2-4. user는 DB에서 읽어온 data이고, req.body가 실제 form으로 입력된 값이므로 각 항목을 덮어 쓰는 부분입니다.

// index.js

// ...
// DB setting ...
// Other settings ...

// Routes
app.use("/", require("./routes/home"));
app.use("/posts", require("./routes/posts"));
app.use("/users", require("./routes/users")); // 1

// Port setting ...

1. 마지막으로 users route를 추가해 줍니다.

코드 - ejs

view들은 post와 비교해서 특별한 것이 전혀 없습니다.

<!-- views/partials/nav.ejs -->

<nav class="navbar navbar-default">
 <div class="container">
  <div class="navbar-header">
   <!-- ... -->
  </div>
  <div class="collapse navbar-collapse" id="myNavbar">
   <ul class="nav navbar-nav">
    <!-- ... -->
   </ul>
   <ul class="nav navbar-nav navbar-right"> <!-- 1 -->
    <li><a href="/users">Users</a></li>
    <li><a href="/users/new">Sign Up</a></li>
   </ul>
  </div>
 </div>
</nav>

navbar-right class를 이용하면 우측정렬된 메뉴를 만들 수 있습니다.

<!-- views/users/index.ejs -->

<!DOCTYPE html>
<html>
 <head>
  <% include ../partials/head %>
 </head>
 <body>
  <% include ../partials/nav %>

  <div class="container user user-index">

   <div class="contentBox">
    <h3 class="contentBoxTop">Users</h3>
    <ul class="floats">
     <% if(users == null || users.length == 0){ %>
      <div class="noData" colspan=100> There is no user yet.</div>
     <% } %>
     <% users.forEach(function(user) { %>
      <li>
       <a href="/users/<%= user.username %>"><%= user.username %></a>
      </li>
     <% }) %>
    </ul>
   </div>

  </div> <!-- container end -->
 </body>
</html>


<!-- views/users/new.ejs -->

<!DOCTYPE html>
<html>
 <head>
  <% include ../partials/head %>
 </head>
 <body>
  <% include ../partials/nav %>

  <div class="container user user-new">

   <form class="user-form form-horizontal" action="/users" method="post">
    <div class="contentBox">
     <h3 class="contentBoxTop">New User</h3>
     <fieldset>
      <div class="form-group">
       <label for="username" class="col-sm-3 control-label">Username*</label>
       <div class="col-sm-9">
        <input class="form-control" type="text" id="username" name="username" value="">
       </div>
      </div>
      <div class="form-group">
       <label for="name" class="col-sm-3 control-label">Name*</label>
       <div class="col-sm-9">
        <input class="form-control" type="text" id="name" name="name" value="">
       </div>
      </div>
      <div class="form-group">
       <label for="email" class="col-sm-3 control-label">Email</label>
       <div class="col-sm-9">
        <input class="form-control" type="text" id="email" name="email" value="">
       </div>
      </div>
      <div class="form-group">
       <label for="password" class="col-sm-3 control-label">Password*</label>
       <div class="col-sm-9">
        <input class="form-control" type="password" id="password" name="password" value="">
       </div>
      </div>
      <div class="form-group">
       <label for="passwordConfirmation" class="col-sm-12 control-label">Password Confirmation*</label>
       <div class="col-sm-9 col-sm-offset-3">
        <input class="form-control" type="password" id="passwordConfirmation" name="passwordConfirmation" value="">
       </div>
      </div>
      <p>
       <small>*Required</small>
      </p>
     </fieldset>
    </div>
    <div class="buttons">
     <button type="submit" class="btn btn-default">Submit</button>
    </div>
   </form>

  </div> <!-- container end -->
 </body>
</html>


<!-- views/users/edit.ejs -->

<!DOCTYPE html>
<html>
 <head>
  <% include ../partials/head %>
 </head>
 <body>
  <% include ../partials/nav %>

  <div class="container user user-edit">

   <div class="buttons">
    <a class="btn btn-default" href="/users/<%= user.username %>">Back</a>
   </div>

   <form class="user-form form-horizontal" action="/users/<%= user.username %>?_method=put" method="post">
    <div class="contentBox">
     <h3 class="contentBoxTop">Edit User</h3>
     <fieldset>
      <div class="form-group">
       <label for="currentPassword" class="col-sm-12 control-label">Current Password*</label>
       <div class="col-sm-9 col-sm-offset-3">
        <input class="form-control" type="password" id="currentPassword" name="currentPassword" value="">
       </div>
      </div>
      <hr></hr>
      <div class="form-group">
       <label for="username" class="col-sm-3 control-label">Username*</label>
       <div class="col-sm-9">
        <input class="form-control" type="text" id="username" name="username" value="<%= user.username %>">
       </div>
      </div>
      <div class="form-group">
       <label for="name" class="col-sm-3 control-label">Name*</label>
       <div class="col-sm-9">
        <input class="form-control" type="text" id="name" name="name" value="<%= user.name %>">
       </div>
      </div>
      <div class="form-group">
       <label for="email" class="col-sm-3 control-label">Email</label>
       <div class="col-sm-9">
        <input class="form-control" type="text" id="email" name="email" value="<%= user.email %>">
       </div>
      </div>
      <div class="form-group">
       <label for="newPassword" class="col-sm-12 control-label">New Password</label>
       <div class="col-sm-9 col-sm-offset-3">
        <input class="form-control" type="password" id="newPassword" name="newPassword" value="">
       </div>
      </div>
      <div class="form-group">
       <label for="passwordConfirmation" class="col-sm-12 control-label">Password Confirmation</label>
       <div class="col-sm-9 col-sm-offset-3">
        <input class="form-control" type="password" id="passwordConfirmation" name="passwordConfirmation" value="">
       </div>
      </div>
      <p>
       <small>*Required</small>
      </p>
     </fieldset>
    </div>
    <div class="buttons">
     <button type="submit" class="btn btn-default">Submit</button>
    </div>
   </form>

  </div> <!-- container end -->
 </body>
</html>


<!-- views/users/show.ejs -->

<!DOCTYPE html>
<html>
 <head>
  <% include ../partials/head %>
 </head>
 <body>
  <% include ../partials/nav %>

  <div class="container user user-show">

   <div class="buttons">
    <a class="btn btn-default" href="/users">Back</a>
    <a class="btn btn-default" href="/users/<%= user.username %>/edit">Edit</a>
   </div>

   <form class="user-form form-horizontal" action="/users" method="post">
    <div class="contentBox">
     <h3 class="contentBoxTop"><%= user.username %></h3>
     <fieldset disabled>
      <div class="form-group">
       <label for="name" class="col-sm-3">Name</label>
       <div class="col-sm-9">
        <input class="form-control" type="text" id="name" name="name" value="<%= user.name %>">
       </div>
      </div>
      <div class="form-group">
       <label for="email" class="col-sm-3">Email</label>
       <div class="col-sm-9">
        <input class="form-control" type="text" id="email" name="email" value="<%= user.email %>">
       </div>
      </div>
     </fieldset>
    </div>
   </form>

  </div> <!-- container end -->
 </body>
</html>

코드 - css

/* public/css/master.css */

/* global style ... */
/* home style ... */
/* post style ... */

/* user style */
.user {
 max-width: 320px;
 font-family: 'Open Sans', sans-serif;
 font-size: 12px;
}
.user-index ul{
 margin: 0;
 padding: 3px 12px;
}
.user-index ul:after { /* 1 */
 content: "";
 display: block;
 clear: both;
}
.user-index ul li{
 display: inline-block;
 list-style-type: none;
 float:left;
}
.user-index ul li a{
 display: inline-block;
 text-decoration:none;
 margin: 3px;
 background-color: #eee;
 padding: 3px 10px;
 border-radius: 3px;
}
.user-index ul li a:hover{
 background-color: #ccc;
}
.user-edit hr{
 margin-top: 5px;
 margin-bottom: 11px;
}

1. 이부분은 float의 parent를 위한 부분입니다. 이렇게 설정해 놓으면 float이 parent밖으로 안튀어나오게 됩니다. CSS 꿀팁이죠.

실행결과

Sign Up 메뉴를 누르면 회원가입 form이 뜹니다.

회원가입이 끝나거나, 메뉴의 Users를 누르면 index가 나옵니다.

show입니다.

edit입니다. 회원가입할때는 password가 필수지만, 정보 수정시의 new password는 필수가 아닙니다.

마치며..

이번 포스팅은 내용이 좀 길긴한데, 자꾸 비슷한 내용을 반복하면서 글을 계속 나눌 수 없어서 한번에 해봤습니다.

긴 강의 읽으시느라 수고하셨습니다. 또한 이번 강의를 보면서 비밀번호를 그대로 저장하는 보안상의 엄청나게 중요한 문제를 발견하신 분들이 있으실텐데, 다음 포스팅에서는 이에 대해 알아보겠습니다.

댓글

박수진 2017.10.07
안녕하세요 궁금한게 생겨 질문 드립니다. user.password = req.body.newPassword? req.body.newPassword : user.password; // 2-3 이 부분에서 ?연산자는 조건문이 true면 :앞을 반환하고 false면 :뒤를 반환한다고 알고있습니다. 근데 user.password = req.body.newPassword이 부분은 어떻게 하면 참이되고 거짓이 되는지 잘 모르겠습니다. =는 대입하는 개념이 아닌가요? 아 그리고 user.originalPassword = user.password; 이걸 하는게 어떤이유때문에 하는건지 모르겠습니다.
I
Ian H 2017.10.10
@박수진,
말씀하신것처럼 삼항연산자는 조건부(?앞부분)가 참이면 콜론(:) 앞의 것을 반환하고 거짓이면 콜론 뒤의 것을 반환합니다. 근데, 자바스크립트에서는 값이 없으면(null, undefined, 빈문자열 등등) 거짓, 값이 있으면 참으로 인식하기 때문에 user.password = req.body.newPassword? req.body.newPassword : user.password 에서는 req.body.newPassword에 값이 있으면 앞의 req.body.newPassword를 반환해서 대입시키고, req.body.newPassword에 값이 없으면 뒤의 user.password를 반환해서 대입시킵니다.
두번째 user.originalPassword = user.password; 는 그냥 일반대입으로 originalPassword에 DB에서 가져온 password를 담아놓는 부분입니다. 왜냐하면, user 수정 form에서 newPassword가 있으면 password에는 newPassword가 들어가기때문에 원래 비밀번호를 따로 담아 놔야 currentPassword와 값이 일치하는지 비교할 수 있기 때문입니다.
L
Lion Cha 2017.10.13
저도 궁금한게 있습니다.   for(var p in req.body){ // 2-4  여기 에서 var p in req.body 는 어떻게 동작되나요? 초보인데 처음보는 구조네요
I
Ian H 2017.10.13
@Lion Cha,
for(var <KEY> in <OBJECT>){<CODE>}는 반복문의 하나입니다. <OBJECT>의 key 목록을 반복하며 <KEY>에 담아 <CODE>에서 사용할 수 있게 해줍니다. 아래 코드를 실행해 보시면 바로 이해가 될 거에요.
var obj = {first: 123, second:"hello", third: true}; for(var key in obj){   console.log(key+":", obj[key]) }
김남현 2017.11.04
// routes/users.js // Index // 1 router.route("/").get(function(req, res){   는
// routes/home.js  router.get("/", function(req, res){       
routes/posts.js router.get("/", function(req, res){
와 다르게 routet.route("/").get( 이라고 표현되었는데, 어떤 의미가 있나요?
김남현 2017.11.04
아, 약간 찾아봤습니다. 우선 이 강의를 좀 더 봐야, 목적을 알수 있겠네요 :)
I
Ian H 2017.11.07
@김남현,
router.route("/")는 route을 쓰는 다른 방법입니다. 이걸 쓰면 .get, .post 등 http method를 chain으로 엮을 수 있습니다.
router.route("/")   .get(function(req, res){     ...   })   .post(function(req, res){     ...   });
이런식으로요. 근데 게시판 강의에서는 활용하지 않고 있어서.. 코드를 수정하였습니다. 쓸대없이 혼란을 드려 죄송합니다!
김남현 2017.11.20
@Ian H,
아, 그렇군요 :) 덕분에 여러 내용들을 찾아볼 수 있어서 좋았습니다. 감사합니다.
쮸리맨 2018.02.14
정말잘보고있습니다! 진짜 잘배우고있어요! 몇개만 질문해봐도될까요 아직많이초보라ㅠ
// New router.get("/new", function(req, res){  res.render("users/new", {user:{}});  }); 유저라우터 new는 디비데이터를 사용안하는데  {user:{}} 이걸 굳이 해준이유가뭐죠? 없어도 상관없는거 아닌가요 ㅠ
그리고  // virtuals // 2 userSchema.virtual("passwordConfirmation") .get(function(){ return this._passwordConfirmation; }) .set(function(value){ this._passwordConfirmation=value; });
이게 디비에는 저장이안되지만  인증을위해  passwordConfirmation  이항목이 사용되는건데 passwordConfirmation 이것과 _passwordConfirmation 이것의 차이가뭐죠  this가 userSchema 이거말하는거아닌가요?  또 여기서 set(function(value){ this._passwordConfirmation=value; });   폼에서 비밀번호확인에 적은걸 여기로 받아오는건 알겠는데   이 value를 넣는 함수는어디있는거죠 ㅠ
  그리고  userSchema.path("password").validate(function(v) { 여기서 매개변수 v 의 쓰임새가뭐죠? 궁금합니다 ㅠ
답답해도 너그러히 이해해주세요 ㅠ 
I
Ian H 2018.02.14
@쮸리맨,
배우는 과정에서 충분히 의문을 가질 수 있는 부분이고 예리한 질문들입니다. 코드의 작동원리를 이해하려고 하는 모습이 아주 바람직합니다.
1. res.render("users/new", {user:{}}) 는 "users/new"에 {}라는 오브젝트를 user라는 이름으로 전달하고 페이지를 제작(render)하는 명령어입니다. 물론 DB에서 불러온 data를 이런식으로 넣어서 보내는 경우가 많긴하지만 반드시 DB와 연결되는 명령어는 아닙니다(db와 연관지어 질문을 하셔서 먼저 부연설명부터 드렸습니다). 사실 이 부분은 제가 쓸대없는 코드를 작성한 경우입니다. 왜냐하면 현재 views/users/new.ejs에는 이 user 오브젝트가 전혀 사용되지 않거든요. 나중에 나오는 강의에서 사용되는데, 그때 넣으면 되지 지금 넣을 필요는 없는 코드입니다. res.render("users/new")로 사용하셔도 무방합니다. 혼란을 드려 죄송합니다!
2. userSchema.virtual("passwordConfirmation") .get(function(){ return this._passwordConfirmation; }) .set(function(value){ this._passwordConfirmation=value; });에서 this._passwordConfirmation는 실제 값이 저장되는 공간이며 passwordConfirmation는 해당 값에 접근할 수 있는 이름입니다. 이부분은 객체지향언어의 getter/setter를 검색해보시면 좀 더 상세히 이해를 하실 수 있을텐데요, 실제 값에 직접적인 접근을 막고 함수를 통해서 값을 넣거나(set), 꺼내도록(get)하고, 부가적으로 값을 넣거나 꺼낼 때 값을 변경할 수 있는 코드를 작성할 수 있습니다. 근데 사실 위에서 보면 '그냥 passwordConfirmation를 사용해서 this._passwordConfirmation에 값을 그대로 넣고 빼고 하는데 무슨 소용이야' 라고 생각하실 수 있으실텐데, 물론 맞는 말입니다. 실재로 위 코드에서는 그러한 이점이 없습니다. 그럼 이렇게 코드를 작성한 이유는? 공식 문서(http://mongoosejs.com/docs/guide.html#virtuals)에서 이렇게 쓰도록 되어 있기 때문입니다.
3. userSchema.path("password").validate(function(v) { 에서 v는 validation시 password의 값을 가지는 변수입니다(이때 변수명 v는 다른 걸로 바꿔도 상관이 없습니다). 하지만 해당 코드에서는 var user = this;를 사용해서 user 전체 오브젝트를 사용하고 v를 사용하고 있진 않습니다. 1번과는 다르게 여기서의 v는 여전히 의미가 있다고 생각합니다. 왜냐하면 userSchema.path("password").validate함수가 password의 값을 언제나 변수로 던져주기 때문입니다. 다만 우리가 사용하지 않을 뿐이죠.
쮸리맨 2018.02.15
@Ian H,
와 답변해주셔서 감사합니다! 제가 이해한게 맞는가싶은데 
두번째 질문에서  즉, 가상으로 항목을 만들때는  " passwordConfirmation " 이걸쓰고  값을 넣을때는 앞에  _  언더바가 자동적으로 붙어서 _ passwordConfirmation 이렇게 되서 이걸 써야하는거죠? 제가 잘이해한게 맞는지몰겠네요.. 
//Post 모델을보니깐 
postSchema.virtual("createdDate") .get(function(){  return getDate(this.createdAt); });
이건 this.createdAt 인데  createdAt 이건 실제스키마에있는 필드이기때문에  _ 언더바를안쓰고  this.createdAt 이렇게쓰는것이고 
만약 질문한예제처럼) 가상항목인 createdDate 를 이용한다하면 언더바를붙여서  this._createdDate
이렇게 해서 값을넣어준다는거죠? _id처럼 디비가붙여주는거네요?
제가 잘이해한건지 모르겠습니다 ㅠㅠ
그리고 .set(function(value){ this._passwordConfirmation=value; }); 에서 폼에서 적은값이 여기 value로 들어가는건가요? 
자꾸 귀찮게해서 죄송합니다 ㅠ
I
Ian H 2018.02.15
@쮸리맨,
virtual 항목은 model에서 필요한 항목이지만 DB에 저장하지 않아도 되는(혹은 저장하지 말아야 하는) 항목들입니다. user.passwordConfirmation은 사용자가 비밀번호를 한번 더 입력하게 하여 제대로 된 값을 입력했는지 확인하는 부분으로 DB에 저장할 필요가 없고 post.createdDate은 DB에 있는 createdAt항목(createdDate가 아닙니다)을 getDate()함수에 넣어서 결과 값을 가져오는 것이기 때문에 virtual로 만들었습니다.
즉 virtual 항목들은 DB랑 직접적으로 관련이 없습니다. 그러므로 언더바를 "DB"가 붙여준다거나 하지 않습니다. 또한 mongoose랑 DB랑 혼동하고 계신 것같은데, mongoose는 DB가 아닙니다. DB를 쉽게 조작할 수 있도록 해주는 ORM입니다. 이 개념이 명확했다면 "_를 mongoose가 붙여주는거네요?"라고 질문해야 옳습니다.(물론 별로 신경안쓰고 질문하다 보니 그냥 쓰신 말일 수도 있겠습니다만 혹시나 차이를 모르실까봐 적어봅니다.)
그리고 언더바(_)는
userSchema.virtual("passwordConfirmation") .get(function(){ return this._passwordConfirmation; }) .set(function(value){ this._passwordConfirmation=value; });
에서 그냥 제가 붙여준 것입니다. 위 코드를 
userSchema.virtual("passwordConfirmation") .get(function(){ return this.abc; }) .set(function(value){ this.abc=value; });
로 고쳐도 결과적으로 같습니다. 다만 이경우 this._passwordConfirmation가 생성되지 않고 this.abc가 생성됩니다. this.abc 선언은 어디에 있나요?-> 없습니다. 자바스크립트는 원래 선언이 없어도 변수를 쓸 수있는 언어이죠.
postSchema.virtual("createdDate") .get(function(){  return getDate(this.createdAt); });
에서 post.createdDate은 get()만 있고 set()이 없으므로 해당 값을 읽을 수만 있고 변경할 수는 없습니다. 그리고 post._createdDate라는 항목도 물론 생성되지 않습니다. 
요약하면 언더바는 사람들이 virtual 항목의 값을 임시로 저장하는 항목임을 표시하기 위해 사용하는 것이지 mongoose가 특별히 처리하는 것은 아닙니다.
마지막으로 value는 passwordConfirmation의 값이 들어가는 것이 맞습니다^^
J
Jung Ae Lee 2018.02.28
제보: 이부분 때문에 메소드 내부에서 password 가 안읽어지던데.. password:{type:String, required:[true,"Password is required!"], select:false}, ---> select:false 제거하면 됨 수고하세요 ..
I
Ian H 2018.03.01
@Jung Ae Lee,
password는 보안상 필요한 곳에서만 읽혀지고 다른 곳에서는 읽혀지지 않게 일부러 막아놓았습니다^^
n
nodemon 2018.05.03
안녕하세요:)  User Error 처리까지봤는데요!! 가입시 입력한 username을 수정할때 edit에서 1회만 수정가능하게 제한하려면 Schema속성중엔 없는거같고 edit.ejs파일에 수정못하게 코드추가하는방법 맞나요? 어디다가 어떤코드를 추가해야될까요~~
I
Ian H 2018.05.03
@nodemon,
안녕하세요^^ DB에 기록하는 것이 최선일 듯 합니다. username을 수정하면 해당유저의 항목에 기록을 해 두었다가 다시는 수정하지 못하게 하는 것이죠.
n
nodemon 2018.05.08
@Ian H,
역시.. 乃  다양한각도로봐야하나봐요 근데 저는아직 헤매고있어요 ㅋㅋ 이해는 했는데 말씀하신걸 구현할줄을모르겠어요ㅠ 할말있는데 영어로 말못하는것처럼요;;
그리고 회원가입할때 input type= select 가 안먹히던데 이방법이아닌가요?  ( class 안에다가 못넣겠어요 select가 따로나오더라구요 )
// models/User.js ...  age:{type:String} ...
// edit.ejs       <div class="form-group">        <label for="age" class="col-sm-3 control-label">분류</label>        <div class="col-sm-9">              <input type="age" class="form-control" id="age" name="age" value="<%= user.age %>">
      <select id="age">            <optgroup label="연령">           <option value="대분류" selected>선택</option>           <option value="10대">10대</option>           <option value="20대">20대</option>           <option value="30대">30대</option>           <option value="40대">40대</option>         </optgroup>       </select>
I
Ian H 2018.05.08
@nodemon,
user schema에 boolean으로 isUsernameUpdated 항목 추가한 후에, 기본값은 false로 하고, username 업데이트 할때 값을 true로 바꾼 후, 만약 isUsernameUpdated가 true면 username을 변경하지 못하게 하면 됩니다^^
I
Ian H 2018.05.08
<select>는 <input>에 연결시키는 게 아니라, select 자체가 form 항목입니다. 예를 들어 <select name="mySelect">이면 선택된 option의 value가 mySelect의 value로 form submit시에 전달됩니다
n
nodemon 2018.05.09
@Ian H,
아아.. 댓글 너무 감사합니다!! 폼2개를 합치려고하니까 안됬군요 select 에다가 담으니까 됬어요!! ^^ "첫 연령선택에따라 달라지는 두번째선택" 의 경우 <select name="age">연령확인후 만약 그 값이 "20대" 라면 <select name="20대옵션" 을 선택가능 이런구조로 되는거지요? 혹시 이러한소스는 어떻게되는지 알수있을까요^^;
I
Ian H 2018.05.09
@nodemon,
해당 부분은 자바스크립트로 처리하셔야 합니다. select에 change 이벤트를 두고 값이 변할때마다 보여줄 내용을 조작하시면 되겠습니다.
M
Min Dal 2018.05.13
안녕하세요 강의 잘 보고 있습니다. 제보할게 있는데요 user 인덱스를 username기준으로 정렬하는 부분에서 코드에선 오름차순인데 설명에선 내림차순이라고 되어있네요 수정 부탁 드립니다
I
Ian H 2018.05.14
@Min Dal,
안녕하세요^^ 게시물에서 해당 부분을 수정하였습니다. 알려주셔서 감사합니다!
깨어있는사람 2018.08.20
안녕하세요 강의 정말 유익하게 잘 따라하며 보고 있습니다.  에러 제보하나 하려고합니다. user정보수정에서 username을 수정하고 save성공하면 show로 리다이렉트되는 부분에서, req.params.username은 정보변경전의 username이므로 에러가 발생합니다. user.username으로 변경하는 것이 맞으려나요?
I
Ian H 2018.08.20
@깨어있는사람,
안녕하세요. 제보주신 내용이 맞습니다. username이 변경될 수 있으므로 req에서 username을 받는 것이 아닌 user.username으로 변경되어야 합니다. 알려주셔서 감사합니다!
-
-1 2019.02.02
if(user.currentPassword && user.currentPassword != user.originalPassword) 여기서 currentpassword는 왜 두개로 && 되는 건가요??  
댓글쓰기

이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기

UP